diff options
Diffstat (limited to '')
26 files changed, 5973 insertions, 1694 deletions
diff --git a/extensions/vertical-workspaces/lib/appDisplay.js b/extensions/vertical-workspaces/lib/appDisplay.js new file mode 100644 index 0000000..2ac70b1 --- /dev/null +++ b/extensions/vertical-workspaces/lib/appDisplay.js @@ -0,0 +1,1474 @@ +/** + * V-Shell (Vertical Workspaces) + * appDisplay.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; + +const { Clutter, GLib, GObject, Meta, Shell, St, Graphene, Pango } = imports.gi; + +const DND = imports.ui.dnd; +const Main = imports.ui.main; +const AppDisplay = imports.ui.appDisplay; +const IconGrid = imports.ui.iconGrid; + +const ExtensionUtils = imports.misc.extensionUtils; +const Me = ExtensionUtils.getCurrentExtension(); +const IconGridOverride = Me.imports.lib.iconGrid; +const _Util = Me.imports.lib.util; + +const DIALOG_SHADE_NORMAL = Clutter.Color.from_pixel(0x00000022); +const DIALOG_SHADE_HIGHLIGHT = Clutter.Color.from_pixel(0x00000000); + +// gettext +const _ = Me.imports.lib.settings._; + +let _overrides; + +let _appGridLayoutSettings; +let _appDisplayScrollConId; +let _appSystemStateConId; +let _appGridLayoutConId; +let _origAppViewItemAcceptDrop; +let _updateFolderIcons; + +let opt; +let shellVersion = _Util.shellVersion; +let _firstRun = true; + +function update(reset = false) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('appDisplayModule', true); + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled + if (_firstRun && reset) + return; + + _firstRun = false; + + if (_overrides) + _overrides.removeAll(); + + if (reset) { + _setAppDisplayOrientation(false); + _updateAppGridProperties(reset); + _updateAppGridDND(reset); + _restoreOverviewGroup(); + _overrides = null; + opt = null; + return; + } + + _overrides = new _Util.Overrides(); + + if (opt.ORIENTATION === Clutter.Orientation.VERTICAL) { + _overrides.addOverride('AppDisplayVertical', AppDisplay.AppDisplay.prototype, AppDisplayVertical); + _overrides.addOverride('BaseAppViewVertical', AppDisplay.BaseAppView.prototype, BaseAppViewVertical); + } + + // 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('FolderView', AppDisplay.FolderView.prototype, FolderView); + _overrides.addOverride('FolderIcon', AppDisplay.FolderIcon.prototype, FolderIcon); + _overrides.addOverride('AppIcon', AppDisplay.AppIcon.prototype, AppIcon); + _overrides.addOverride('AppDisplay', AppDisplay.AppDisplay.prototype, AppDisplayCommon); + _overrides.addOverride('AppViewItem', AppDisplay.AppViewItem.prototype, AppViewItemCommon); + _overrides.addOverride('BaseAppViewCommon', AppDisplay.BaseAppView.prototype, BaseAppViewCommon); + + _setAppDisplayOrientation(opt.ORIENTATION === Clutter.Orientation.VERTICAL); + _updateAppGridProperties(); + _updateAppGridDND(); + opt._appGridNeedsRedisplay = true; +} + +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 + if (appDisplay._hintContainer && appDisplay._hintContainer.get_parent()) + 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(); + if (appDisplay._hintContainer && !appDisplay._hintContainer.get_parent()) { + scrollContainer.add_child(appDisplay._hintContainer); + // the hit container covers the entire app grid and added at the top of the stack blocks DND drops + // so it needs to be pushed below + scrollContainer.set_child_below_sibling(appDisplay._hintContainer, null); + } + + 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) { + opt._appGridNeedsRedisplay = false; + // columns, rows, icon size + const appDisplay = Main.overview._overview._controls._appDisplay; + appDisplay.visible = true; + + if (reset) { + appDisplay._grid.layoutManager.fixedIconSize = -1; + appDisplay._grid.layoutManager.allow_incomplete_pages = true; + appDisplay._grid.setGridModes(); + if (_appGridLayoutSettings) { + _appGridLayoutSettings.disconnect(_appGridLayoutConId); + _appGridLayoutConId = 0; + _appGridLayoutSettings = null; + } + appDisplay._redisplay(); + + appDisplay._grid.set_style(''); + _resetAppGrid(); + } else { + // update grid on layout reset + if (!_appGridLayoutSettings) { + _appGridLayoutSettings = ExtensionUtils.getSettings('org.gnome.shell'); + _appGridLayoutConId = _appGridLayoutSettings.connect('changed::app-picker-layout', _resetAppGrid); + } + + appDisplay._grid.layoutManager.allow_incomplete_pages = opt.APP_GRID_ALLOW_INCOMPLETE_PAGES; + appDisplay._grid.set_style(`column-spacing: ${opt.APP_GRID_SPACING}px; row-spacing: ${opt.APP_GRID_SPACING}px;`); + + // force redisplay + appDisplay._grid._currentMode = -1; + appDisplay._grid.setGridModes(); + appDisplay._grid.layoutManager.fixedIconSize = opt.APP_GRID_ICON_SIZE; + // appDisplay._folderIcons.forEach(folder => folder._dialog?._updateFolderSize()); + _resetAppGrid(); + } +} + +function _updateAppGridDND(reset) { + if (!reset) { + if (!_appSystemStateConId && opt.APP_GRID_INCLUDE_DASH >= 3) { + _appSystemStateConId = Shell.AppSystem.get_default().connect( + 'app-state-changed', + () => { + _updateFolderIcons = true; + Main.overview._overview._controls._appDisplay._redisplay(); + } + ); + } + } else if (_appSystemStateConId) { + Shell.AppSystem.get_default().disconnect(_appSystemStateConId); + _appSystemStateConId = 0; + } + if (opt.APP_GRID_ORDER && !reset) { + if (!_origAppViewItemAcceptDrop) + _origAppViewItemAcceptDrop = AppDisplay.AppViewItem.prototype.acceptDrop; + AppDisplay.AppViewItem.prototype.acceptDrop = () => false; + } else if (_origAppViewItemAcceptDrop) { + AppDisplay.AppViewItem.prototype.acceptDrop = _origAppViewItemAcceptDrop; + } +} + +function _restoreOverviewGroup() { + Main.overview.dash.showAppsButton.checked = false; + Main.layoutManager.overviewGroup.opacity = 255; + Main.layoutManager.overviewGroup.scale_x = 1; + Main.layoutManager.overviewGroup.hide(); +} + +const AppDisplayVertical = { + // correction of the appGrid size when page indicators were moved from the bottom to the right + adaptToSize(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); + }, +}; + +const AppDisplayCommon = { + _ensureDefaultFolders() { + // disable creation of default folders if user deleted them + }, + + _redisplay() { + this._folderIcons.forEach(icon => { + icon.view._redisplay(); + }); + + BaseAppViewCommon._redisplay.bind(this)(); + }, + + // 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_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); + }); + } else if (_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 + _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_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); + + return appIcons; + }, + + // support active preview icons + _onDragBegin(overview, source) { + if (source._sourceItem) + source = source._sourceItem; + + this._dragMonitor = { + dragMotion: this._onDragMotion.bind(this), + }; + DND.addDragMonitor(this._dragMonitor); + if (shellVersion < 43) + this._slideSidePages(AppDisplay.SidePages.PREVIOUS | AppDisplay.SidePages.NEXT | AppDisplay.SidePages.DND); + else + 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 preview + acceptDrop(source) { + if (opt.APP_GRID_ORDER) + return false; + if (source._sourceItem) + source = source._sourceItem; + + let dropTarget = null; + if (shellVersion >= 43) { + dropTarget = this._dropTarget; + delete this._dropTarget; + } + + if (!this._canAccept(source)) + return false; + + if ((shellVersion < 43 && this._dropPage) || + (shellVersion >= 43 && (dropTarget === this._prevPageIndicator || + dropTarget === this._nextPageIndicator))) { + let increment; + + if (shellVersion < 43) + increment = this._dropPage === AppDisplay.SidePages.NEXT ? 1 : -1; + else + 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) { + log(`Warning:${e}`); + } + this._removeDelayedMove(); + } + + this._savePages(); + + let 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; + }, +}; + +const BaseAppViewVertical = { + after__init() { + this._grid.layoutManager._orientation = Clutter.Orientation.VERTICAL; + this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.EXTERNAL); + this._orientation = Clutter.Orientation.VERTICAL; + this._swipeTracker.orientation = Clutter.Orientation.VERTICAL; + this._swipeTracker._reset(); + this._pageIndicators.vertical = true; + this._box.vertical = false; + this._pageIndicators.x_expand = false; + this._pageIndicators.y_align = Clutter.ActorAlign.CENTER; + this._pageIndicators.x_align = Clutter.ActorAlign.START; + this._pageIndicators.set_style('margin-right: 10px;'); + const scrollContainer = this._scrollView.get_parent(); + if (shellVersion < 43) { + // remove touch friendly side navigation bars / arrows + if (this._hintContainer && this._hintContainer.get_parent()) + scrollContainer.remove_child(this._hintContainer); + } else { + // moving these bars needs more patching of the this's code + // for now we just change bars style to be more like vertically oriented arrows indicating direction to prev/next page + this._nextPageIndicator.add_style_class_name('nextPageIndicator'); + this._prevPageIndicator.add_style_class_name('prevPageIndicator'); + } + + // setting their x_scale to 0 removes the arrows and avoid allocation issues compared to .hide() them + this._nextPageArrow.scale_x = 0; + this._prevPageArrow.scale_x = 0; + + this._adjustment = this._scrollView.vscroll.adjustment; + + this._adjustment.connect('notify::value', adj => { + const value = adj.value / adj.page_size; + this._pageIndicators.setCurrentPosition(value); + }); + }, + // <= 42 only, this fixes dnd from appDisplay to the workspace thumbnail on the left if appDisplay is on page 1 because of appgrid left overshoot + _pageForCoords() { + return AppDisplay.SidePages.NONE; + }, +}; + +const BaseAppViewCommon = { + _sortOrderedItemsAlphabetically(icons = null) { + if (!icons) + icons = this._orderedItems; + icons.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); + }, + + _setLinearPositions(icons) { + const { itemsPerPage } = this._grid; + icons.forEach((icon, i) => { + const page = Math.floor(i / itemsPerPage); + const position = i % itemsPerPage; + try { + this._moveItem(icon, page, position); + } catch (e) { + log(`Warning:${e}`); + } + }); + }, + + // adds sorting options and option to add favorites and running apps + _redisplay() { + 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); + } else { + // App is part of a folder + } + }); + + // sort all alphabetically + if (opt.APP_GRID_ORDER > 0) { + // const { itemsPerPage } = this._grid; + let appIcons = this._orderedItems; + this._sortOrderedItemsAlphabetically(appIcons); + // 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_DASH_FIRST) { + 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_DASH_FIRST) + appIcons.sort((a, b) => a.app.get_state() !== Shell.AppState.RUNNING && b.app.get_state() === Shell.AppState.RUNNING); + + this._setLinearPositions(appIcons); + + this._orderedItems = appIcons; + } + + this.emit('view-loaded'); + if (!opt.APP_GRID_ALLOW_INCOMPLETE_PAGES) { + for (let i = 0; i < this._grid.nPages; i++) + this._grid.layoutManager._fillItemVacancies(i); + } + }, + + _canAccept(source) { + return opt.APP_GRID_ORDER ? false : source instanceof AppDisplay.AppViewItem; + }, + + // support active preview icons + acceptDrop(source) { + if (!this._canAccept(source)) + return false; + + if (source._sourceItem) + source = source._sourceItem; + + + if (this._dropPage) { + const increment = this._dropPage === AppDisplay.SidePages.NEXT ? 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; + + this._moveItem(source, page, position); + 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 (shellVersion < 43) { + this._dropPage = this._pageForCoords(dragEvent.x, dragEvent.y); + if (this._dropPage && + this._dropPage === AppDisplay.SidePages.PREVIOUS && + this._grid.currentPage === 0) { + delete this._dropPage; + return DND.DragMotionResult.NO_DROP; + } + } + + if (appIcon instanceof AppDisplay.AppViewItem) { + if (shellVersion < 44) { + // Handle the drag overshoot. When dragging to above the + // icon grid, move to the page above; when dragging below, + // move to the page below. + this._handleDragOvershoot(dragEvent); + } else 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(); + } + } + + this._maybeMoveItem(dragEvent); + + return DND.DragMotionResult.CONTINUE; + }, + + // adjustable page width for GS <= 42 + adaptToSize(width, height, isFolder = false) { + 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(); + + let pageWidth, pageHeight; + + pageHeight = availHeight; + pageWidth = Math.ceil(availWidth * (isFolder ? 1 : opt.APP_GRID_PAGE_WIDTH_SCALE)); + // subtract space for navigation arrows in horizontal mode + pageWidth -= opt.ORIENTATION ? 0 : 128; + + 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 + }, +}; + +const 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); + }, +}; + +const FolderIcon = { + after__init() { + /* // If folder preview icons are clickable, + // disable opening the folder with primary mouse button and enable the secondary one + const buttonMask = opt.APP_GRID_ACTIVE_PREVIEW + ? St.ButtonMask.TWO | St.ButtonMask.THREE + : St.ButtonMask.ONE | St.ButtonMask.TWO; + this.button_mask = buttonMask;*/ + this.button_mask = St.ButtonMask.ONE | St.ButtonMask.TWO; + + // build the folders now to avoid node errors when dragging active folder preview icons + if (this.visible && opt.APP_GRID_ACTIVE_PREVIEW) + this._ensureFolderDialog(); + }, + + open() { + this._ensureFolderDialog(); + if (this._dialog._designCapacity !== this.view._orderedItems.length) + this._dialog._updateFolderSize(); + + this.view._scrollView.vscroll.adjustment.value = 0; + this._dialog.popup(); + }, +}; + +const FolderView = { + _createGrid() { + let grid; + if (shellVersion < 43) + grid = new FolderGrid(); + else + grid = new FolderGrid43(); + + // IconGrid algorithm for adaptive icon size + // counts with different default(max) size for folders + grid.layoutManager._isFolder = true; + 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 ActiveFolderIcon(app); + child._sourceItem = this._orderedItems[i]; + child._sourceFolder = this; + child.icon.style_class = ''; + child.icon.set_style('margin: 0; padding: 0;'); + child.icon.setIconSize(subSize); + + bin.child = child; + + bin.connect('enter-event', () => { + bin.ease({ + duration: 100, + scale_x: 1.14, + scale_y: 1.14, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + }); + bin.connect('leave-event', () => { + bin.ease({ + duration: 100, + scale_x: 1, + scale_y: 1, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + }); + } + } + + layout.attach(bin, rtl ? (i + 1) % gridSize : i % gridSize, Math.floor(i / gridSize), 1, 1); + } + + // if folder content changed, update folder size + if (this._dialog && this._dialog._designCapacity !== this._orderedItems.length) + this._dialog._updateFolderSize(); + + return icon; + }, + + // this just overrides _redisplay() for GS < 44 + _redisplay() { + // super._redisplay(); - super doesn't work in my overrides + AppDisplay.BaseAppView.prototype._redisplay.bind(this)(); + }, + + _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 (!AppDisplay._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); + }); + this._appIds = this._apps.map(app => app.get_id()); + return items; + }, + + // 42 only - don't apply appGrid scale on folders + adaptToSize(width, height) { + if (!opt.ORIENTATION) { + const [, indicatorHeight] = this._pageIndicators.get_preferred_height(-1); + height -= indicatorHeight; + } + BaseAppViewCommon.adaptToSize.bind(this)(width, height, true); + }, +}; + +// folder columns and rows +const FolderGrid = GObject.registerClass( +class FolderGrid extends IconGrid.IconGrid { + _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 + columns_per_page: opt.APP_GRID_FOLDER_COLUMNS ? opt.APP_GRID_FOLDER_COLUMNS : 20, + rows_per_page: opt.APP_GRID_FOLDER_ROWS ? opt.APP_GRID_FOLDER_ROWS : 20, + page_halign: Clutter.ActorAlign.CENTER, + page_valign: Clutter.ActorAlign.CENTER, + }); + + // if (!opt.APP_GRID_FOLDER_DEFAULT) + const spacing = opt.APP_GRID_SPACING; + this.set_style(`column-spacing: ${spacing}px; row-spacing: ${spacing}px;`); + this.layout_manager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; + } + + adaptToSize(width, height) { + this.layout_manager.adaptToSize(width, height); + } +}); + + +let FolderGrid43; +// first reference to constant defined using const in other module returns undefined, the AppGrid const will remain empty and unused +const AppGrid = 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_FOLDER_COLUMNS ? opt.APP_GRID_FOLDER_COLUMNS : 20, + rows_per_page: opt.APP_GRID_FOLDER_ROWS ? opt.APP_GRID_FOLDER_ROWS : 20, + page_halign: Clutter.ActorAlign.CENTER, + page_valign: Clutter.ActorAlign.CENTER, + }); + + const spacing = opt.APP_GRID_SPACING; + this.set_style(`column-spacing: ${spacing}px; row-spacing: ${spacing}px;`); + this.layout_manager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; + + this.setGridModes([ + { + columns: opt.APP_GRID_FOLDER_COLUMNS ? opt.APP_GRID_FOLDER_COLUMNS : 3, + rows: opt.APP_GRID_FOLDER_ROWS ? opt.APP_GRID_FOLDER_ROWS : 3, + }, + ]); + } + + adaptToSize(width, height) { + this.layout_manager.adaptToSize(width, height); + } + }); +} + +const FOLDER_DIALOG_ANIMATION_TIME = 200; // AppDisplay.FOLDER_DIALOG_ANIMATION_TIME +const AppFolderDialog = { + // injection to _init() + after__init() { + // 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); + }, + + popup() { + if (this._isOpen) + return; + + /* if (!this._correctSize) { + // update folder with the precise app item size when the dialog is realized + GLib.idle_add(0, () => this._updateFolderSize(true)); + this._correctSize = true; + }*/ + + this._isOpen = this._grabHelper.grab({ + actor: this, + onUngrab: () => this.popdown(), + }); + + if (!this._isOpen) + return; + + this.get_parent().set_child_above_sibling(this, null); + + this._needsZoomAndFade = true; + this.show(); + + this.emit('open-state-changed', true); + }, + + _updateFolderSize() { + // adapt folder size according to the settings and number of icons + const view = this._view; + view._grid.layoutManager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; + view._grid.set_style(`column-spacing: ${opt.APP_GRID_SPACING}px; row-spacing: ${opt.APP_GRID_SPACING}px;`); + + const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); + const dialogMargin = 30; + const nItems = view._orderedItems.length; + let columns = opt.APP_GRID_FOLDER_COLUMNS; + let rows = opt.APP_GRID_FOLDER_ROWS; + let spacing = opt.APP_GRID_SPACING; + const monitor = global.display.get_monitor_geometry(global.display.get_primary_monitor()); + + if (!columns && !rows) { + 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) { + columns = Math.ceil(nItems / rows); + } else if (columns && !rows) { + rows = Math.ceil(nItems / columns); + } + + const iconSize = opt.APP_GRID_FOLDER_ICON_SIZE < 0 ? opt.APP_GRID_FOLDER_ICON_SIZE_DEFAULT : opt.APP_GRID_FOLDER_ICON_SIZE; + let itemSize = iconSize + 53; // icon padding + // 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._notFirstRun) { + const [firstItem] = view._grid.layoutManager._container; + firstItem.icon.setIconSize(iconSize); + const [firstItemWidth] = firstItem.get_preferred_size(); + const realSize = firstItemWidth / scaleFactor; + if (realSize > iconSize) + itemSize = realSize; + } else { + this._needsUpdateSize = true; + this._notFirstRun = true; + } + + + let width = columns * (itemSize + spacing) + /* padding for nav arrows*/64; + width = Math.round(width + (opt.ORIENTATION || !opt.APP_GRID_FOLDER_COLUMNS ? 100 : 160/* space for navigation arrows*/)); + let height = rows * (itemSize + spacing) + /* header*/75 + /* padding*/100; + + // folder must fit the primary monitor + // reduce columns/rows if needed and count with the scaled values + while (width * scaleFactor > monitor.width - 2 * dialogMargin) { + width -= itemSize + spacing; + columns -= 1; + } + while (height * scaleFactor > monitor.height - 2 * dialogMargin) { + height -= itemSize + spacing; + rows -= 1; + } + width = Math.max(540, width); + + const layoutManager = view._grid.layoutManager; + layoutManager.rows_per_page = rows; + layoutManager.columns_per_page = columns; + + // this line is required by GS 43 + view._grid.setGridModes([{ columns, rows }]); + + this.child.set_style(` + width: ${width}px; + height: ${height}px; + padding: 30px; + `); + + view._redisplay(); + + // store original item count + this._designCapacity = nItems; + }, + + _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; + if (!opt.APP_GRID_FOLDER_CENTER) { + const appDisplay = this._source._parentView; + + dialogTargetX = Math.round(sourceCenterX - this.child.width / 2); + dialogTargetY = Math.round(sourceCenterY - this.child.height / 2); + + // keep the dialog in appDisplay area if possible + dialogTargetX = Math.clamp( + dialogTargetX, + this.x + appDisplay.x, + this.x + appDisplay.x + appDisplay.width - this.child.width + ); + + dialogTargetY = Math.clamp( + dialogTargetY, + this.y + appDisplay.y, + this.y + appDisplay.y + appDisplay.height - this.child.height + ); + // or at least in the monitor area + const monitor = global.display.get_monitor_geometry(global.display.get_primary_monitor()); + dialogTargetX = Math.clamp( + dialogTargetX, + this.x + monitor.x, + this.x + monitor.x + monitor.width - this.child.width + ); + + dialogTargetY = Math.clamp( + dialogTargetY, + this.y + monitor.y, + this.y + monitor.y + monitor.height - this.child.height + ); + } + const dialogOffsetX = -dialogX + dialogTargetX; + const dialogOffsetY = -dialogY + dialogTargetY; + + 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: DIALOG_SHADE_NORMAL, + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + + 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, + onComplete: () => { + // if the folder grid was build with the estimated icon item size because the real size wasn't available + // rebuild it with the real size now, after the folder was realized + if (this._needsUpdateSize) { + this._updateFolderSize(); + this._view._redisplay(); + this._needsUpdateSize = false; + } + }, + }); + + 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; + } + + let [sourceX, sourceY] = + this._source.get_transformed_position(); + let [dialogX, dialogY] = + this.child.get_transformed_position(); + + this.ease({ + background_color: Clutter.Color.from_pixel(0x00000000), + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + + 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_OUT_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 = []; + }, + }); + + this._needsZoomAndFade = false; + }, + + _setLighterBackground(lighter) { + const backgroundColor = lighter + ? DIALOG_SHADE_HIGHLIGHT + : DIALOG_SHADE_NORMAL; + + this.ease({ + backgroundColor, + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + }, +}; + +// just make app grid to update all invalid positions that may be result of grid/icon size change +function _updateIconPositions() { + const appDisplay = Main.overview._overview._controls._appDisplay; + const icons = [...appDisplay._orderedItems]; + for (let i = 0; i < icons.length; i++) + appDisplay._moveItem(icons[i], -1, -1); +} + +function _removeIcons() { + const appDisplay = Main.overview._overview._controls._appDisplay; + const icons = [...appDisplay._orderedItems]; + for (let i = 0; i < icons.length; i++) { + const icon = icons[i]; + if (icon._dialog) + Main.layoutManager.overviewGroup.remove_child(icon._dialog); + appDisplay._removeItem(icon); + icon.destroy(); + } + appDisplay._folderIcons = []; +} + +function _resetAppGrid(settings) { + const appDisplay = Main.overview._overview._controls._appDisplay; + // reset the grid only if called directly without args or if all folders where removed by using reset button in Settings window + // otherwise this function is called every time a user moves icon to another position as a settings callback + if (settings) { + const currentValue = JSON.stringify(global.settings.get_value('app-picker-layout').deep_unpack()); + const emptyValue = JSON.stringify([]); + const customLayout = currentValue !== emptyValue; + // appDisplay._customLayout = customLayout; + if (customLayout) + return; + else + opt._appGridNeedsRedisplay = true; + } + + // force update icon size using adaptToSize(). the page size cannot be the same as the current one + appDisplay._grid.layoutManager._pageWidth += 1; + appDisplay._grid.layoutManager.adaptToSize(appDisplay._grid.layoutManager._pageWidth - 1, appDisplay._grid.layoutManager._pageHeight); + _removeIcons(); + appDisplay._redisplay(); + // force appDisplay to move all icons to proper positions and update all properties + GLib.idle_add(0, () => { + _updateIconPositions(); + if (appDisplay._sortOrderedItemsAlphabetically) { + appDisplay._sortOrderedItemsAlphabetically(); + appDisplay._grid.layoutManager._pageWidth += 1; + appDisplay._grid.layoutManager.adaptToSize(appDisplay._grid.layoutManager._pageWidth - 1, appDisplay._grid.layoutManager._pageHeight); + appDisplay._setLinearPositions(appDisplay._orderedItems); + } else { + appDisplay._removeItem(appDisplay._orderedItems[0]); + appDisplay._redisplay(); + } + + appDisplay._redisplay(); + }); +} + +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; +} + +const AppIcon = { + after__init() { + // update the app label behavior + this._updateMultiline(); + }, + + // avoid accepting by placeholder when dragging active preview + // and also by icon if alphabet or usage sorting are used + _canAccept(source) { + if (source._sourceItem) + source = source._sourceItem; + let view = AppDisplay._getViewFromIcon(source); + + return source !== this && + (source instanceof this.constructor) && + (view instanceof AppDisplay.AppDisplay && + !opt.APP_GRID_ORDER); + }, +}; + +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 + ? AppDisplay.APP_ICON_TITLE_EXPAND_TIME + : AppDisplay.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_ORDER) + 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 + if (source._sourceItem) { + const app = source._sourceItem.app; + source._sourceFolder.removeApp(app); + } + + return true; + }, + +}; + +const ActiveFolderIcon = GObject.registerClass( +class ActiveFolderIcon extends AppDisplay.AppIcon { + _init(app) { + super._init(app, { + setSizeManually: true, + showLabel: false, + }); + } + + handleDragOver() { + return DND.DragMotionResult.CONTINUE; + } + + acceptDrop() { + return false; + } + + _onDragEnd() { + this._dragging = false; + this.undoScaleAndFade(); + Main.overview.endItemDrag(this._sourceItem.icon); + } +}); diff --git a/extensions/vertical-workspaces/lib/appFavorites.js b/extensions/vertical-workspaces/lib/appFavorites.js new file mode 100644 index 0000000..50ebce9 --- /dev/null +++ b/extensions/vertical-workspaces/lib/appFavorites.js @@ -0,0 +1,61 @@ +/** + * V-Shell (Vertical Workspaces) + * appFavorites.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; + +const { Shell } = imports.gi; +const AppFavorites = imports.ui.appFavorites; +const Main = imports.ui.main; + +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const _Util = Me.imports.lib.util; + +let opt; +let _overrides; +let _firstRun = true; + +function update(reset = false) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('appFavoritesModule', true); + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled + if (_firstRun && reset) + return; + + _firstRun = false; + + if (_overrides) + _overrides.removeAll(); + + + // if notifications are enabled no override is needed + if (reset || opt.SHOW_FAV_NOTIFICATION) { + _overrides = null; + opt = null; + return; + } + + _overrides = new _Util.Overrides(); + + // AppFavorites.AppFavorites is const, first access returns undefined + const dummy = AppFavorites.AppFavorites; + _overrides.addOverride('AppFavorites', AppFavorites.AppFavorites.prototype, AppFavoritesCommon); +} + +const AppFavoritesCommon = { + addFavoriteAtPos(appId, pos) { + this._addFavorite(appId, pos); + }, + + removeFavorite(appId) { + this._removeFavorite(appId); + }, +}; diff --git a/extensions/vertical-workspaces/dash.js b/extensions/vertical-workspaces/lib/dash.js index c4e3101..bf832bd 100644 --- a/extensions/vertical-workspaces/dash.js +++ b/extensions/vertical-workspaces/lib/dash.js @@ -1,5 +1,5 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * dash.js * * @author GdH <G-dH@github.com> @@ -8,7 +8,7 @@ * modified dash module of https://github.com/RensAlthuis/vertical-overview extension */ -const { Clutter, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi; +const { Clutter, GObject, St, Shell, Meta } = imports.gi; const AppDisplay = imports.ui.appDisplay; const AppFavorites = imports.ui.appFavorites; const DND = imports.ui.dnd; @@ -16,42 +16,54 @@ const IconGrid = imports.ui.iconGrid; const Main = imports.ui.main; const Overview = imports.ui.overview; const Dash = imports.ui.dash; -const { DashIcon, DashItemContainer, getAppFromSource, DragPlaceholderItem } = imports.ui.dash; +const PopupMenu = imports.ui.popupMenu; +const { AppMenu } = imports.ui.appMenu; +const BoxPointer = imports.ui.boxpointer; +const AltTab = imports.ui.altTab; const Me = imports.misc.extensionUtils.getCurrentExtension(); -const Util = Me.imports.util; -const _ = Me.imports.settings._; +const Util = Me.imports.lib.util; +const _ = Me.imports.lib.settings._; -let verticalOverrides = {}; +const shellVersion = Util.shellVersion; let _origWorkId; let _newWorkId; let _showAppsIconBtnPressId; -// added values to achieve better ability to scale down according the available space +// added values to achieve a better ability to scale down according to available space var BaseIconSizes = [16, 24, 32, 40, 44, 48, 56, 64, 72, 80, 96, 112, 128]; -const RecentFilesSearchProviderPrefix = Me.imports.recentFilesSearchProvider.prefix; -const WindowSearchProviderPrefix = Me.imports.windowSearchProvider.prefix; +const RecentFilesSearchProviderPrefix = Me.imports.lib.recentFilesSearchProvider.prefix; +const WindowSearchProviderPrefix = Me.imports.lib.windowSearchProvider.prefix; let _overrides; +let opt; +let _firstRun = true; const DASH_ITEM_LABEL_SHOW_TIME = 150; -let opt; - function update(reset = false) { - if (_overrides) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('dashModule', true); + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled + if (_firstRun && reset) + return; + + _firstRun = false; + + if (_overrides) _overrides.removeAll(); - } - - opt = Me.imports.settings.opt; + + const dash = Main.overview._overview._controls.layoutManager._dash; setToHorizontal(); - dash.remove_style_class_name("vertical-overview"); - dash.remove_style_class_name("vertical-overview-left"); - dash.remove_style_class_name("vertical-overview-right"); + dash.remove_style_class_name('vertical'); + dash.remove_style_class_name('vertical-left'); + dash.remove_style_class_name('vertical-right'); if (reset) { _moveDashAppGridIcon(reset); @@ -60,6 +72,7 @@ function update(reset = false) { _updateRecentFilesIcon(false); dash.visible = true; dash._background.opacity = 255; + dash._background.remove_style_class_name('v-shell-dash-background'); _overrides = null; opt = null; return; @@ -67,13 +80,15 @@ function update(reset = false) { _overrides = new Util.Overrides(); - _overrides.addOverride('DashItemContainer', Dash.DashItemContainer.prototype, DashItemContainerOverride); - _overrides.addOverride('DashCommon', Dash.Dash.prototype, DashCommonOverride); + _overrides.addOverride('DashItemContainer', Dash.DashItemContainer.prototype, DashItemContainerCommon); + _overrides.addOverride('DashCommon', Dash.Dash.prototype, DashCommon); + _overrides.addOverride('AppIcon', AppDisplay.AppIcon.prototype, AppIconCommon); + _overrides.addOverride('DashIcon', Dash.DashIcon.prototype, DashIconCommon); if (opt.DASH_VERTICAL) { _overrides.addOverride('Dash', Dash.Dash.prototype, DashOverride); setToVertical(); - dash.add_style_class_name("vertical-overview"); + dash.add_style_class_name('vertical'); if (!_newWorkId) { _origWorkId = dash._workId; @@ -93,14 +108,15 @@ function update(reset = false) { _moveDashAppGridIcon(); _connectShowAppsIcon(); - if (dash._showWindowsIcon && !dash._showWindowsIconClickedId) { + if (dash._showWindowsIcon && !dash._showWindowsIconClickedId) dash._showWindowsIconClickedId = dash._showWindowsIcon.toggleButton.connect('clicked', (a, c) => c && Util.activateSearchProvider(WindowSearchProviderPrefix)); - } - if (dash._recentFilesIcon && !dash._recentFilesIconClickedId) { + + if (dash._recentFilesIcon && !dash._recentFilesIconClickedId) dash._recentFilesIconClickedId = dash._recentFilesIcon.toggleButton.connect('clicked', (a, c) => c && Util.activateSearchProvider(RecentFilesSearchProviderPrefix)); - } - Main.overview.dash._redisplay(); - Main.overview._overview._controls.layoutManager._dash.visible = opt.DASH_VISIBLE; + + dash.visible = opt.DASH_VISIBLE; + dash._background.add_style_class_name('v-shell-dash-background'); + dash._redisplay(); } function setToVertical() { @@ -128,13 +144,13 @@ function setToVertical() { dash._queueRedisplay(); dash._adjustIconSize(); - dash.add_style_class_name(opt.DASH_LEFT ? 'vertical-overview-left' : 'vertical-overview-right'); + dash.add_style_class_name(opt.DASH_LEFT ? 'vertical-left' : 'vertical-right'); } function setToHorizontal() { let dash = Main.overview._overview._controls.layoutManager._dash; if (_origWorkId) - dash._workId = _origWorkId; //pretty sure this is a leak, but there no provided way to disconnect these... + dash._workId = _origWorkId; // pretty sure this is a leak, but there no provided way to disconnect these... dash._box.layout_manager.orientation = Clutter.Orientation.HORIZONTAL; dash._dashContainer.layout_manager.orientation = Clutter.Orientation.HORIZONTAL; dash._dashContainer.y_expand = true; @@ -173,7 +189,7 @@ function _moveDashAppGridIcon(reset = false) { const index = dash._dashContainer.get_children().length - 1; dash._dashContainer.set_child_at_index(dash._showAppsIcon, index); } - if (!reset && appIconPosition === 2) {// 2 - hide + if (!reset && appIconPosition === 2) { // 2 - hide const style = opt.DASH_VERTICAL ? 'show-apps-icon-vertical-hide' : 'show-apps-icon-horizontal-hide'; dash._showAppsIcon.add_style_class_name(style); // for some reason even if the icon height in vertical mode should be set to 0 by the style, it stays visible in full size returning height 1px @@ -191,29 +207,27 @@ function _connectShowAppsIcon(reset = false) { Main.overview.dash._showAppsIcon.reactive = true; _showAppsIconBtnPressId = Main.overview.dash._showAppsIcon.connect('button-press-event', (actor, event) => { const button = event.get_button(); - if (button === Clutter.BUTTON_MIDDLE) { + if (button === Clutter.BUTTON_MIDDLE) Util.openPreferences(); - } else if (button === Clutter.BUTTON_SECONDARY) { + else if (button === Clutter.BUTTON_SECONDARY) Util.activateSearchProvider(WindowSearchProviderPrefix); - } else { + else return Clutter.EVENT_PROPAGATE; - } + return Clutter.EVENT_STOP; }); - } else { - if (_showAppsIconBtnPressId) { - Main.overview.dash._showAppsIcon.disconnect(_showAppsIconBtnPressId); - _showAppsIconBtnPressId = 0; - Main.overview.dash._showAppsIcon.reactive = false; - } + } else if (_showAppsIconBtnPressId) { + Main.overview.dash._showAppsIcon.disconnect(_showAppsIconBtnPressId); + _showAppsIconBtnPressId = 0; + Main.overview.dash._showAppsIcon.reactive = false; } } -var DashOverride = { - handleDragOver: function (source, actor, _x, y, _time) { - let app = getAppFromSource(source); +const DashOverride = { + handleDragOver(source, actor, _x, y, _time) { + let app = Dash.getAppFromSource(source); // Don't allow favoriting of transient apps - if (app == null || app.is_window_backed()) + if (app === null || app.is_window_backed()) return DND.DragMotionResult.NO_DROP; if (!global.settings.is_writable('favorite-apps')) @@ -257,7 +271,7 @@ var DashOverride = { this._dragPlaceholderPos = pos; // Don't allow positioning before or after self - if (favPos != -1 && (pos == favPos || pos == favPos + 1)) { + if (favPos !== -1 && (pos === favPos || pos === favPos + 1)) { this._clearDragPlaceholder(); return DND.DragMotionResult.CONTINUE; } @@ -273,7 +287,7 @@ var DashOverride = { fadeIn = true; } - this._dragPlaceholder = new DragPlaceholderItem(); + this._dragPlaceholder = new Dash.DragPlaceholderItem(); this._dragPlaceholder.child.set_width(this.iconSize / 2); this._dragPlaceholder.child.set_height(this.iconSize); this._box.insert_child_at_index(this._dragPlaceholder, @@ -284,7 +298,7 @@ var DashOverride = { if (!this._dragPlaceholder) return DND.DragMotionResult.NO_DROP; - let srcIsFavorite = favPos != -1; + let srcIsFavorite = favPos !== -1; if (srcIsFavorite) return DND.DragMotionResult.MOVE_DROP; @@ -292,7 +306,7 @@ var DashOverride = { return DND.DragMotionResult.COPY_DROP; }, - _redisplay: function () { + _redisplay() { let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); let running = this._appSystem.get_running(); @@ -343,7 +357,7 @@ var DashOverride = { let newApp = newApps.length > newIndex ? newApps[newIndex] : null; // No change at oldIndex/newIndex - if (oldApp == newApp) { + if (oldApp === newApp) { oldIndex++; newIndex++; continue; @@ -361,7 +375,7 @@ var DashOverride = { addedItems.push({ app: newApp, item: this._createAppItem(newApp), - pos: newIndex + pos: newIndex, }); newIndex++; continue; @@ -370,10 +384,10 @@ var DashOverride = { // App moved let nextApp = newApps.length > newIndex + 1 ? newApps[newIndex + 1] : null; - let insertHere = nextApp && nextApp == oldApp; + let insertHere = nextApp && nextApp === oldApp; let alreadyRemoved = removedActors.reduce((result, actor) => { let removedApp = actor.child._delegate.app; - return result || removedApp == newApp; + return result || removedApp === newApp; }, false); if (insertHere || alreadyRemoved) { @@ -381,7 +395,7 @@ var DashOverride = { addedItems.push({ app: newApp, item: newItem, - pos: newIndex + removedActors.length + pos: newIndex + removedActors.length, }); newIndex++; } else { @@ -439,10 +453,10 @@ var DashOverride = { width: this.iconSize, height: 1, }); - this._box.add_child(this._separator) + this._box.add_child(this._separator); } - //FIXME: separator placement is broken (also in original dash) + // FIXME: separator placement is broken (also in original dash) let pos = nFavorites; if (this._dragPlaceholder) pos++; @@ -456,8 +470,8 @@ var DashOverride = { this._box.queue_relayout(); }, - _createAppItem: function (app) { - let appIcon = new DashIcon(app); + _createAppItem(app) { + let appIcon = new Dash.DashIcon(app); let indicator = appIcon._dot; indicator.x_align = opt.DASH_LEFT ? Clutter.ActorAlign.START : Clutter.ActorAlign.END; @@ -468,7 +482,7 @@ var DashOverride = { this._itemMenuStateChanged(item, opened); }); - let item = new DashItemContainer(); + let item = new Dash.DashItemContainer(); item.setChild(appIcon); // Override default AppIcon label_actor, now the @@ -480,12 +494,12 @@ var DashOverride = { this._hookUpLabel(item, appIcon); return item; - } -} + }, +}; -var DashItemContainerOverride = { +const DashItemContainerCommon = { // move labels according dash position - showLabel: function() { + showLabel() { if (!this._labelText) return; @@ -500,7 +514,7 @@ var DashItemContainerOverride = { const labelWidth = this.label.get_width(); const labelHeight = this.label.get_height(); - const xOffset = Math.floor((itemWidth - labelWidth) / 2); + let xOffset = Math.floor((itemWidth - labelWidth) / 2); let x = Math.clamp(stageX + xOffset, 0, global.stage.width - labelWidth); let node = this.label.get_theme_node(); @@ -509,23 +523,18 @@ var DashItemContainerOverride = { if (opt.DASH_TOP) { const yOffset = itemHeight - labelHeight + 3 * node.get_length('-y-offset'); y = stageY + yOffset; - } else if (opt.DASH_BOTTOM) { const yOffset = node.get_length('-y-offset'); y = stageY - this.label.height - yOffset; - } else if (opt.DASH_RIGHT) { const yOffset = Math.floor((itemHeight - labelHeight) / 2); - - const xOffset = 4; + xOffset = 4; x = stageX - xOffset - this.label.width; y = Math.clamp(stageY + yOffset, 0, global.stage.height - labelHeight); - - } if (opt.DASH_LEFT) { + } else if (opt.DASH_LEFT) { const yOffset = Math.floor((itemHeight - labelHeight) / 2); - - const xOffset = 4; + xOffset = 4; x = stageX + this.width + xOffset; y = Math.clamp(stageY + yOffset, 0, global.stage.height - labelHeight); @@ -544,11 +553,17 @@ var DashItemContainerOverride = { duration: DASH_ITEM_LABEL_SHOW_TIME, mode: Clutter.AnimationMode.EASE_OUT_QUAD, }); - } -} + }, +}; + +const DashCommon = { + // use custom BaseIconSizes and add support for custom icons + _adjustIconSize() { + // if a user launches multiple apps at once, this function may be called again before the previous call has finished + // as a result, new icons will not reach their full size, or will be missing, if adding a new icon and changing the dash size due to lack of space at the same time + if (this._adjustingInProgress) + return; -var DashCommonOverride = { - _adjustIconSize: function () { // For the icon size, we only consider children which are "proper" // icons (i.e. ignoring drag placeholders) and which are not // animating out (which means they will be destroyed at the end of @@ -560,18 +575,19 @@ var DashCommonOverride = { !actor.animatingOut; }); - // add new custom icons into the calculation - if (this._showAppsIcon.visible) { + // add new custom icons to the list + if (this._showAppsIcon.visible) iconChildren.push(this._showAppsIcon); - } - if (this._showWindowsIcon) { + + if (this._showWindowsIcon) iconChildren.push(this._showWindowsIcon); - } - if (this._recentFilesIcon) { + + if (this._recentFilesIcon) iconChildren.push(this._recentFilesIcon); - } - if (!iconChildren.length) return; + + if (!iconChildren.length) + return; if (this._maxWidth === -1 || this._maxHeight === -1) return; @@ -593,12 +609,14 @@ var DashCommonOverride = { let firstButton = iconChildren[0].child; let firstIcon = firstButton._delegate.icon; - if (!firstIcon.icon) return; + if (!firstIcon.icon) + return; // Enforce valid spacings during the size request firstIcon.icon.ensure_style(); const [, , iconWidth, iconHeight] = firstIcon.icon.get_preferred_size(); const [, , buttonWidth, buttonHeight] = firstButton.get_preferred_size(); + let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; let availWidth, availHeight, maxIconSize; if (dashHorizontal) { @@ -614,7 +632,7 @@ var DashCommonOverride = { availHeight -= themeNode.get_vertical_padding(); availHeight -= buttonHeight - iconHeight; - maxIconSize = Math.min(availWidth / iconChildren.length, availHeight, opt.MAX_ICON_SIZE); + maxIconSize = Math.min(availWidth / iconChildren.length, availHeight, opt.MAX_ICON_SIZE * scaleFactor); } else { availWidth = this._maxWidth; availWidth -= this._background.get_theme_node().get_horizontal_padding(); @@ -626,10 +644,9 @@ var DashCommonOverride = { (iconChildren.length - 1) * spacing + 2 * this._background.get_theme_node().get_vertical_padding(); - maxIconSize = Math.min(availWidth, availHeight / iconChildren.length, opt.MAX_ICON_SIZE); + maxIconSize = Math.min(availWidth, availHeight / iconChildren.length, opt.MAX_ICON_SIZE * scaleFactor); } - let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; let iconSizes = BaseIconSizes.map(s => s * scaleFactor); let newIconSize = BaseIconSizes[0]; @@ -638,8 +655,11 @@ var DashCommonOverride = { newIconSize = BaseIconSizes[i]; } - /*if (newIconSize == this.iconSize) - return;*/ + if (newIconSize === this.iconSize) + return; + + // set the in-progress state here after all the possible cancels + this._adjustingInProgress = true; let oldIconSize = this.iconSize; this.iconSize = newIconSize; @@ -665,7 +685,7 @@ var DashCommonOverride = { // Scale the icon's texture to the previous size and // tween to the new size icon.icon.set_size(icon.icon.width * scale, - icon.icon.height * scale); + icon.icon.height * scale); icon.icon.ease({ width: targetWidth, @@ -683,23 +703,351 @@ var DashCommonOverride = { mode: Clutter.AnimationMode.EASE_OUT_QUAD, }); } + + this._adjustingInProgress = false; + }, +}; + +const DashIconCommon = { + after__init() { + if (opt.DASH_ICON_SCROLL) { + this._scrollConId = this.connect('scroll-event', _onScrollEvent.bind(this)); + this._leaveConId = this.connect('leave-event', _onLeaveEvent.bind(this)); + } }, +}; + +function _onScrollEvent(source, event) { + if ((this.app && !opt.DASH_ICON_SCROLL) || (this._isSearchWindowsIcon && !opt.SEARCH_WINDOWS_ICON_SCROLL)) { + if (this._scrollConId) + this.disconnect(this._scrollConId); + if (this._leaveConId) + this.disconnect(this._leaveConId); + return Clutter.EVENT_PROPAGATE; + } + + let direction = Util.getScrollDirection(event); + if (direction === Clutter.ScrollDirection.UP) + direction = 1; + else if (direction === Clutter.ScrollDirection.DOWN) + direction = -1; + else + return Clutter.EVENT_STOP; + + // avoid uncontrollable switching if smooth scroll wheel or trackpad is used + if (this._lastScroll && Date.now() - this._lastScroll < 160) + return Clutter.EVENT_STOP; + + this._lastScroll = Date.now(); + + _switchWindow.bind(this)(direction); + return Clutter.EVENT_STOP; } -function _updateSearchWindowsIcon(show = opt.SHOW_WINDOWS_ICON) { +function _onLeaveEvent() { + if (!this._selectedMetaWin || this.has_pointer || this.toggleButton?.has_pointer) + return; + + this._selectedPreview._activateSelected = false; + this._selectedMetaWin = null; + this._scrolledWindows = null; + _showWindowPreview.bind(this)(null); +} + +function _switchWindow(direction) { + if (!this._scrolledWindows) { + // source is app icon + if (this.app) { + this._scrolledWindows = this.app.get_windows(); + const wsList = []; + this._scrolledWindows.forEach(w => { + const ws = w.get_workspace(); + if (!wsList.includes(ws)) + wsList.push(ws); + }); + // sort windows by workspaces in MRU order + this._scrolledWindows.sort((a, b) => wsList.indexOf(a.get_workspace()) > wsList.indexOf(b.get_workspace())); + // source is Search Windows icon + } else if (this._isSearchWindowsIcon) { + if (opt.SEARCH_WINDOWS_ICON_SCROLL === 1) // all windows + this._scrolledWindows = AltTab.getWindows(null); + else + this._scrolledWindows = AltTab.getWindows(global.workspace_manager.get_active_workspace()); + } + } + let windows = this._scrolledWindows; + + if (!windows.length) + return; + + // if window selection is in the process, the previewed window must be the current one + let currentWin = this._selectedMetaWin ? this._selectedMetaWin : windows[0]; + + const currentIdx = windows.indexOf(currentWin); + let targetIdx = currentIdx + direction; + + if (targetIdx > windows.length - 1) + targetIdx = 0; + else if (targetIdx < 0) + targetIdx = windows.length - 1; + + const metaWin = windows[targetIdx]; + _showWindowPreview.bind(this)(metaWin); + this._selectedMetaWin = metaWin; +} + +function _showWindowPreview(metaWin) { + const views = Main.overview._overview.controls._workspacesDisplay._workspacesViews; + const viewsIter = [views[0]]; + // secondary monitors use different structure + views.forEach(v => { + if (v._workspacesView) + viewsIter.push(v._workspacesView); + }); + + viewsIter.forEach(view => { + // if workspaces are on primary monitor only + if (!view || !view._workspaces) + return; + + view._workspaces.forEach(ws => { + ws._windows.forEach(windowPreview => { + // metaWin === null resets opacity + let opacity = metaWin ? 50 : 255; + windowPreview._activateSelected = false; + + // minimized windows are invisible if windows are not exposed (WORKSPACE_MODE === 0) + if (!windowPreview.opacity) + windowPreview.opacity = 255; + + // app windows set to lower opacity, so they can be recognized + if (this._scrolledWindows && this._scrolledWindows.includes(windowPreview.metaWindow)) { + if (opt.DASH_ICON_SCROLL === 2) + opacity = 254; + } + if (windowPreview.metaWindow === metaWin) { + if (metaWin && metaWin.get_workspace() !== global.workspace_manager.get_active_workspace()) + Main.wm.actionMoveWorkspace(metaWin.get_workspace()); + + windowPreview.get_parent().set_child_above_sibling(windowPreview, null); + + opacity = 255; + this._selectedPreview = windowPreview; + windowPreview._activateSelected = true; + } + + // if windows are exposed, highlight selected using opacity + if ((opt.OVERVIEW_MODE && opt.WORKSPACE_MODE) || !opt.OVERVIEW_MODE) { + if (metaWin && opacity === 255) + windowPreview.showOverlay(true); + else + windowPreview.hideOverlay(true); + windowPreview.ease({ + duration: 200, + opacity, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + }); + }); + }); +} + +const AppIconCommon = { + activate(button) { + const event = Clutter.get_current_event(); + const state = event ? event.get_state() : 0; + const isMiddleButton = button && button === Clutter.BUTTON_MIDDLE; + const isCtrlPressed = Util.isCtrlPressed(state); + const isShiftPressed = Util.isShiftPressed(state); + const openNewWindow = (this.app.can_open_new_window() && + this.app.state === Shell.AppState.RUNNING && + (isCtrlPressed || isMiddleButton) && !opt.DASH_CLICK_ACTION === 2) || + (opt.DASH_CLICK_ACTION === 2 && !this._selectedMetaWin && !isMiddleButton); + + const currentWS = global.workspace_manager.get_active_workspace(); + const appRecentWorkspace = _getAppRecentWorkspace(this.app); + // this feature shouldn't affect search results, dash icons don't have labels, so we use them as a condition + const showWidowsBeforeActivation = opt.DASH_CLICK_ACTION === 1 && !this.icon.label; + + 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 DASH_CLICK_ACTION == "SHOW_WINS_BEFORE", the app has more than one window and has no window on the current workspace, + // don't activate the app immediately, only move the overview to the workspace with the app's recent window + } else if (showWidowsBeforeActivation && !isShiftPressed && this.app.get_n_windows() > 1 && !targetWindowOnCurrentWs/* && !(opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE)*/) { + // this._scroll = true; + // this._scrollTime = Date.now(); + Main.wm.actionMoveWorkspace(appRecentWorkspace); + Main.overview.dash.showAppsButton.checked = false; + return; + } else if (this._selectedMetaWin) { + this._selectedMetaWin.activate(global.get_current_time()); + } else if (showWidowsBeforeActivation && opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE && !isShiftPressed && this.app.get_n_windows() > 1) { + // expose windows + Main.overview._overview._controls._thumbnailsBox._activateThumbnailAtPoint(0, 0, global.get_current_time(), true); + 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() { + this.app.get_windows().forEach(w => w.change_workspace(global.workspace_manager.get_active_workspace())); + }, + + popupMenu(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) {} + 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 ( Shift + Click )'), 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; + }, +}; + +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; +} + +function _updateSearchWindowsIcon(show = opt.SHOW_WINDOWS_ICON) { const dash = Main.overview._overview._controls.layoutManager._dash; const dashContainer = dash._dashContainer; if (dash._showWindowsIcon) { dashContainer.remove_child(dash._showWindowsIcon); - dash._showWindowsIconClickedId && dash._showWindowsIcon.toggleButton.disconnect(dash._showWindowsIconClickedId); + if (dash._showWindowsIconClickedId) + dash._showWindowsIcon.toggleButton.disconnect(dash._showWindowsIconClickedId); dash._showWindowsIconClickedId = undefined; - dash._showWindowsIcon && dash._showWindowsIcon.destroy(); + if (dash._showWindowsIcon) + dash._showWindowsIcon.destroy(); dash._showWindowsIcon = undefined; } - if (!show || !opt.WINDOW_SEARCH_PROVIDER_ENABLED) return; + if (!show || !opt.WINDOW_SEARCH_PROVIDER_ENABLED) + return; if (!dash._showWindowsIcon) { dash._showWindowsIcon = new ShowWindowsIcon(); @@ -708,22 +1056,23 @@ function _updateSearchWindowsIcon(show = opt.SHOW_WINDOWS_ICON) { dash._hookUpLabel(dash._showWindowsIcon); } - dash._showWindowsIcon.icon.setIconSize(opt.MAX_ICON_SIZE); + dash._showWindowsIcon.icon.setIconSize(dash.iconSize); if (opt.SHOW_WINDOWS_ICON === 1) { dashContainer.set_child_at_index(dash._showWindowsIcon, 0); } else if (opt.SHOW_WINDOWS_ICON === 2) { - index = dashContainer.get_children().length - 1; + const index = dashContainer.get_children().length - 1; dashContainer.set_child_at_index(dash._showWindowsIcon, index); } Main.overview._overview._controls.layoutManager._dash._adjustIconSize(); } -var ShowWindowsIcon = GObject.registerClass( +const ShowWindowsIcon = GObject.registerClass( class ShowWindowsIcon extends Dash.DashItemContainer { _init() { super._init(); + this._isSearchWindowsIcon = true; this._labelText = _('Search Open Windows (Hotkey: Space)'); this.toggleButton = new St.Button({ style_class: 'show-apps', @@ -744,6 +1093,12 @@ class ShowWindowsIcon extends Dash.DashItemContainer { this.toggleButton._delegate = this; this.setChild(this.toggleButton); + + if (opt.SEARCH_WINDOWS_ICON_SCROLL) { + this.reactive = true; + this._scrollConId = this.connect('scroll-event', _onScrollEvent.bind(this)); + this._leaveConId = this.connect('leave-event', _onLeaveEvent.bind(this)); + } } _createIcon(size) { @@ -758,19 +1113,21 @@ class ShowWindowsIcon extends Dash.DashItemContainer { }); function _updateRecentFilesIcon(show = opt.SHOW_RECENT_FILES_ICON) { - const dash = Main.overview._overview._controls.layoutManager._dash; const dashContainer = dash._dashContainer; if (dash._recentFilesIcon) { dashContainer.remove_child(dash._recentFilesIcon); - dash._recentFilesIconClickedId && dash._recentFilesIcon.toggleButton.disconnect(dash._recentFilesIconClickedId); + if (dash._recentFilesIconClickedId) + dash._recentFilesIcon.toggleButton.disconnect(dash._recentFilesIconClickedId); dash._recentFilesIconClickedId = undefined; - dash._recentFilesIcon && dash._recentFilesIcon.destroy(); + if (dash._recentFilesIcon) + dash._recentFilesIcon.destroy(); dash._recentFilesIcon = undefined; } - if (!show || !opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED) return; + if (!show || !opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED) + return; if (!dash._recentFilesIcon) { dash._recentFilesIcon = new ShowRecentFilesIcon(); @@ -779,18 +1136,18 @@ function _updateRecentFilesIcon(show = opt.SHOW_RECENT_FILES_ICON) { dash._hookUpLabel(dash._recentFilesIcon); } - dash._recentFilesIcon.icon.setIconSize(opt.MAX_ICON_SIZE); + dash._recentFilesIcon.icon.setIconSize(dash.iconSize); if (opt.SHOW_RECENT_FILES_ICON === 1) { dashContainer.set_child_at_index(dash._recentFilesIcon, 0); } else if (opt.SHOW_RECENT_FILES_ICON === 2) { - index = dashContainer.get_children().length - 1; + const index = dashContainer.get_children().length - 1; dashContainer.set_child_at_index(dash._recentFilesIcon, index); } Main.overview._overview._controls.layoutManager._dash._adjustIconSize(); } -var ShowRecentFilesIcon = GObject.registerClass( +const ShowRecentFilesIcon = GObject.registerClass( class ShowRecentFilesIcon extends Dash.DashItemContainer { _init() { super._init(); diff --git a/extensions/vertical-workspaces/lib/iconGrid.js b/extensions/vertical-workspaces/lib/iconGrid.js new file mode 100644 index 0000000..1aa980e --- /dev/null +++ b/extensions/vertical-workspaces/lib/iconGrid.js @@ -0,0 +1,314 @@ +/** + * V-Shell (Vertical Workspaces) + * iconGrid.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; +const { GLib, St, Meta } = imports.gi; +const IconGrid = imports.ui.iconGrid; +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const _Util = Me.imports.lib.util; +const shellVersion = _Util.shellVersion; + +// 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, + 48: 48, + TINY: 32, +}; + +const PAGE_WIDTH_CORRECTION = 100; + +let opt; +let _overrides; +let _firstRun = true; + +function update(reset = false) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('appDisplayModule', true); + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled + if (_firstRun && reset) + return; + + _firstRun = false; + + if (_overrides) + _overrides.removeAll(); + + + if (reset) { + _overrides = null; + opt = null; + return; + } + + _overrides = new _Util.Overrides(); + + if (shellVersion < 43 && IconGridCommon._findBestModeForSize) { + IconGridCommon['findBestModeForSize'] = IconGridCommon._findBestModeForSize; + IconGridCommon['_findBestModeForSize'] = undefined; + } + _overrides.addOverride('IconGrid', IconGrid.IconGrid.prototype, IconGridCommon); + _overrides.addOverride('IconGridLayout', IconGrid.IconGridLayout.prototype, IconGridLayoutCommon); +} +// workaround - silence page -2 error on gnome 43 while cleaning app grid + +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 = 53 * 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; + + const spacing = opt.APP_GRID_SPACING; + const iconSize = (opt.APP_GRID_ICON_SIZE > 0 ? opt.APP_GRID_ICON_SIZE : opt.APP_GRID_ICON_SIZE_DEFAULT) * scaleFactor; + // 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) { + columns = Math.floor(width / (iconSize + iconPadding)) + 1; + while (unusedSpaceH < 0) { + columns -= 1; + unusedSpaceH = width - columns * (iconSize + iconPadding) - (columns - 1) * spacing; + } + } + if (!rows) { + rows = Math.floor(height / (iconSize + iconPadding)) + 1; + while (unusedSpaceV < 0) { + rows -= 1; + unusedSpaceV = height - rows * (iconSize + iconPadding) - (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; + const columnSpacingPerPage = opt.APP_GRID_SPACING * (nColumns - 1); + const rowSpacingPerPage = opt.APP_GRID_SPACING * (nRows - 1); + const iconPadding = 53 * 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, the whole range is for the main grid with active folders + if (this._isFolder) + iconSizes = iconSizes.slice(iconSizes.indexOf(IconSize.LARGE), -1); + + let sizeInvalid = false; + for (const size of iconSizes) { + let usedWidth, usedHeight; + + if (firstItem) { + firstItem.icon.setIconSize(size); + const [firstItemWidth, firstItemHeight] = + firstItem.get_preferred_size(); + + const itemSize = Math.max(firstItemWidth, firstItemHeight); + 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)) { + log(`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)) { + log(`iconGrid: Item ${item} already added to IconGridLayout`); + return; + // throw new Error(`Item ${item} already added to IconGridLayout`); + } + + if (page > this._pages.length) { + log(`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)) { + log(`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; + }, +}; diff --git a/extensions/vertical-workspaces/lib/layout.js b/extensions/vertical-workspaces/lib/layout.js new file mode 100644 index 0000000..f6562fd --- /dev/null +++ b/extensions/vertical-workspaces/lib/layout.js @@ -0,0 +1,380 @@ +/** + * V-Shell (Vertical Workspaces) + * layout.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; + +const { Meta, GLib, Shell, Clutter, GObject } = imports.gi; + +const Main = imports.ui.main; +const Layout = imports.ui.layout; +const Ripples = imports.ui.ripples; +const DND = imports.ui.dnd; + +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const _Util = Me.imports.lib.util; + +let _overrides; +let _timeouts; +let opt; +let _firstRun = true; +let _originalUpdateHotCorners; + +function update(reset = false) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('layoutModule', true); + const conflict = _Util.getEnabledExtensions('custom-hot-corners').length || + _Util.getEnabledExtensions('dash-to-panel').length; + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled or in conflict + if (_firstRun && (reset || conflict)) + return; + + _firstRun = false; + + if (!_originalUpdateHotCorners) + _originalUpdateHotCorners = Layout.LayoutManager.prototype._updateHotCorners; + + if (_overrides) + _overrides.removeAll(); + + if (_timeouts) { + Object.values(_timeouts).forEach(t => { + if (t) + GLib.source_remove(t); + }); + } + + if (reset) { + _overrides = null; + opt = null; + _timeouts = null; + Main.layoutManager._updateHotCorners = _originalUpdateHotCorners; + Main.layoutManager._updateHotCorners(); + return; + } + + _timeouts = {}; + + _overrides = new _Util.Overrides(); + _overrides.addOverride('LayoutManager', Layout.LayoutManager.prototype, LayoutManagerCommon); + + Main.layoutManager._updateHotCorners = LayoutManagerCommon._updateHotCorners.bind(Main.layoutManager); + + Main.layoutManager._updatePanelBarrier(); + Main.layoutManager._updateHotCorners(); +} + +const LayoutManagerCommon = { + _updatePanelBarrier() { + if (this._rightPanelBarrier) { + this._rightPanelBarrier.destroy(); + this._rightPanelBarrier = null; + } + + if (this._leftPanelBarrier) { + this._leftPanelBarrier.destroy(); + this._leftPanelBarrier = null; + } + + if (!this.primaryMonitor || !opt) + return; + + if (this.panelBox.height) { + let primary = this.primaryMonitor; + if ([0, 1, 3].includes(opt.HOT_CORNER_POSITION)) { + this._rightPanelBarrier = new Meta.Barrier({ + display: global.display, + x1: primary.x + primary.width, y1: this.panelBox.allocation.y1, + x2: primary.x + primary.width, y2: this.panelBox.allocation.y2, + directions: Meta.BarrierDirection.NEGATIVE_X, + }); + } + + if ([2, 4].includes(opt.HOT_CORNER_POSITION)) { + this._leftPanelBarrier = new Meta.Barrier({ + display: global.display, + x1: primary.x, y1: this.panelBox.allocation.y1, + x2: primary.x, y2: this.panelBox.allocation.y2, + directions: Meta.BarrierDirection.POSITIVE_X, + }); + } + } + }, + + _updateHotCorners() { + // avoid errors if called from foreign override + if (!opt) + return; + // destroy old hot corners + this.hotCorners.forEach(corner => corner?.destroy()); + this.hotCorners = []; + + if (!this._interfaceSettings.get_boolean('enable-hot-corners')) { + this.emit('hot-corners-changed'); + return; + } + + let size = this.panelBox.height; + + // position 0 - default, 1-TL, 2-TR, 3-BL, 4-BR + const position = opt.HOT_CORNER_POSITION; + + // build new hot corners + for (let i = 0; i < this.monitors.length; i++) { + let monitor = this.monitors[i]; + let cornerX, cornerY; + + if (position === 0) { + cornerX = this._rtl ? monitor.x + monitor.width : monitor.x; + cornerY = monitor.y; + } else if (position === 1) { + cornerX = monitor.x; + cornerY = monitor.y; + } else if (position === 2) { + cornerX = monitor.x + monitor.width; + cornerY = monitor.y; + } else if (position === 3) { + cornerX = monitor.x; + cornerY = monitor.y + monitor.height; + } else { + cornerX = monitor.x + monitor.width; + cornerY = monitor.y + monitor.height; + } + + let haveCorner = true; + + if (i !== this.primaryIndex) { + // Check if we have a top left (right for RTL) corner. + // I.e. if there is no monitor directly above or to the left(right) + let besideX = this._rtl ? monitor.x + 1 : cornerX - 1; + let besideY = cornerY; + let aboveX = cornerX; + let aboveY = cornerY - 1; + + for (let j = 0; j < this.monitors.length; j++) { + if (i === j) + continue; + let otherMonitor = this.monitors[j]; + if (besideX >= otherMonitor.x && + besideX < otherMonitor.x + otherMonitor.width && + besideY >= otherMonitor.y && + besideY < otherMonitor.y + otherMonitor.height) { + haveCorner = false; + break; + } + if (aboveX >= otherMonitor.x && + aboveX < otherMonitor.x + otherMonitor.width && + aboveY >= otherMonitor.y && + aboveY < otherMonitor.y + otherMonitor.height) { + haveCorner = false; + break; + } + } + } + + if (haveCorner) { + let corner = new HotCorner(this, monitor, cornerX, cornerY); + corner.setBarrierSize(size); + this.hotCorners.push(corner); + } else { + this.hotCorners.push(null); + } + } + + this.emit('hot-corners-changed'); + }, +}; + +var HotCorner = GObject.registerClass( +class HotCorner extends Layout.HotCorner { + _init(layoutManager, monitor, x, y) { + super._init(layoutManager, monitor, x, y); + + let angle = 0; + switch (opt.HOT_CORNER_POSITION) { + case 2: + angle = 90; + break; + case 3: + angle = 270; + break; + case 4: + angle = 180; + break; + } + + this._ripples._ripple1.rotation_angle_z = angle; + this._ripples._ripple2.rotation_angle_z = angle; + this._ripples._ripple3.rotation_angle_z = angle; + } + + setBarrierSize(size) { + if (this._verticalBarrier) { + this._pressureBarrier.removeBarrier(this._verticalBarrier); + this._verticalBarrier.destroy(); + this._verticalBarrier = null; + } + + if (this._horizontalBarrier) { + this._pressureBarrier.removeBarrier(this._horizontalBarrier); + this._horizontalBarrier.destroy(); + this._horizontalBarrier = null; + } + + if (size > 0) { + const primaryMonitor = global.display.get_primary_monitor(); + const monitor = this._monitor; + const extendV = opt && opt.HOT_CORNER_EDGE && opt.DASH_VERTICAL && monitor.index === primaryMonitor; + const extendH = opt && opt.HOT_CORNER_EDGE && !opt.DASH_VERTICAL && monitor.index === primaryMonitor; + + if (opt.HOT_CORNER_POSITION <= 1) { + this._verticalBarrier = new Meta.Barrier({ + display: global.display, + x1: this._x, x2: this._x, y1: this._y, y2: this._y + (extendV ? monitor.height : size), + directions: Meta.BarrierDirection.POSITIVE_X, + }); + this._horizontalBarrier = new Meta.Barrier({ + display: global.display, + x1: this._x, x2: this._x + (extendH ? monitor.width : size), y1: this._y, y2: this._y, + directions: Meta.BarrierDirection.POSITIVE_Y, + }); + } else if (opt.HOT_CORNER_POSITION === 2) { + this._verticalBarrier = new Meta.Barrier({ + display: global.display, + x1: this._x, x2: this._x, y1: this._y, y2: this._y + (extendV ? monitor.height : size), + directions: Meta.BarrierDirection.NEGATIVE_X, + }); + this._horizontalBarrier = new Meta.Barrier({ + display: global.display, + x1: this._x - size, x2: this._x, y1: this._y, y2: this._y, + directions: Meta.BarrierDirection.POSITIVE_Y, + }); + } else if (opt.HOT_CORNER_POSITION === 3) { + this._verticalBarrier = new Meta.Barrier({ + display: global.display, + x1: this._x, x2: this._x, y1: this._y, y2: this._y - size, + directions: Meta.BarrierDirection.POSITIVE_X, + }); + this._horizontalBarrier = new Meta.Barrier({ + display: global.display, + x1: this._x, x2: this._x + (extendH ? monitor.width : size), y1: this._y, y2: this._y, + directions: Meta.BarrierDirection.NEGATIVE_Y, + }); + } else if (opt.HOT_CORNER_POSITION === 4) { + this._verticalBarrier = new Meta.Barrier({ + display: global.display, + x1: this._x, x2: this._x, y1: this._y, y2: this._y - size, + directions: Meta.BarrierDirection.NEGATIVE_X, + }); + this._horizontalBarrier = new Meta.Barrier({ + display: global.display, + x1: this._x, x2: this._x - size, y1: this._y, y2: this._y, + directions: Meta.BarrierDirection.NEGATIVE_Y, + }); + } + + this._pressureBarrier.addBarrier(this._verticalBarrier); + this._pressureBarrier.addBarrier(this._horizontalBarrier); + } + } + + _toggleOverview() { + if (!opt.HOT_CORNER_ACTION || (!opt.HOT_CORNER_FULLSCREEN && this._monitor.inFullscreen && !Main.overview.visible)) + return; + + if (Main.overview.shouldToggleByCornerOrButton()) { + if ((opt.HOT_CORNER_ACTION === 1 && !_Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 2 && _Util.isCtrlPressed())) + this._toggleWindowPicker(true); + else if ((opt.HOT_CORNER_ACTION === 2 && !_Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 1 && _Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 3 && _Util.isCtrlPressed())) + this._toggleApplications(true); + else if (opt.HOT_CORNER_ACTION === 3 && !_Util.isCtrlPressed()) + this._toggleWindowSearchProvider(); + if (opt.HOT_CORNER_RIPPLES && Main.overview.animationInProgress) + this._ripples.playAnimation(this._x, this._y); + } + } + + _toggleWindowPicker(leaveOverview = false) { + if (Main.overview._shown && (leaveOverview || !Main.overview.dash.showAppsButton.checked)) { + Main.overview.hide(); + } else if (Main.overview.dash.showAppsButton.checked) { + Main.overview.dash.showAppsButton.checked = false; + } else { + const focusWindow = global.display.get_focus_window(); + // at least GS 42 is unable to show overview in X11 session if VirtualBox Machine window grabbed keyboard + if (!Meta.is_wayland_compositor() && focusWindow && focusWindow.wm_class.includes('VirtualBox Machine')) { + // following should help when windowed VBox Machine has focus. + global.stage.set_key_focus(Main.panel); + // key focus doesn't take the effect immediately, we must wait for it + // still looking for better solution! + _timeouts.releaseKeyboardTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, + // delay cannot be too short + 200, + () => { + Main.overview.show(); + + _timeouts.releaseKeyboardTimeoutId = 0; + return GLib.SOURCE_REMOVE; + } + ); + } else { + Main.overview.show(); + } + } + } + + _toggleApplications(leaveOverview = false) { + if ((leaveOverview && Main.overview._shown) || Main.overview.dash.showAppsButton.checked) { + Main.overview.hide(); + } else { + const focusWindow = global.display.get_focus_window(); + // at least GS 42 is unable to show overview in X11 session if VirtualBox Machine window grabbed keyboard + if (!Meta.is_wayland_compositor() && focusWindow && focusWindow.wm_class.includes('VirtualBox Machine')) { + // following should help when windowed VBox Machine has focus. + global.stage.set_key_focus(Main.panel); + // key focus doesn't take the effect immediately, we must wait for it + // still looking for better solution! + _timeouts.releaseKeyboardTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, + // delay cannot be too short + 200, + () => { + Main.overview.show(2); + + _timeouts.releaseKeyboardTimeoutId = 0; + return GLib.SOURCE_REMOVE; + } + ); + } else if (Main.overview._shown) { + Main.overview.dash.showAppsButton.checked = true; + } else { + Main.overview.show(2); // 2 for App Grid + } + } + } + + _toggleWindowSearchProvider() { + if (!Main.overview._overview._controls._searchController._searchActive) { + this._toggleWindowPicker(); + const prefix = 'wq// '; + const position = prefix.length; + const searchEntry = Main.overview.searchEntry; + searchEntry.set_text(prefix); + // searchEntry.grab_key_focus(); + searchEntry.get_first_child().set_cursor_position(position); + searchEntry.get_first_child().set_selection(position, position); + } else { + // Main.overview.searchEntry.text = ''; + Main.overview.hide(); + } + } +}); diff --git a/extensions/vertical-workspaces/lib/messageTray.js b/extensions/vertical-workspaces/lib/messageTray.js new file mode 100644 index 0000000..b35541a --- /dev/null +++ b/extensions/vertical-workspaces/lib/messageTray.js @@ -0,0 +1,67 @@ +/** + * V-Shell (Vertical Workspaces) + * messageTray.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; + +const { Clutter } = imports.gi; +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const Main = imports.ui.main; + +let opt; +let _firstRun = true; + +function update(reset = false) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('messageTrayModule', true); + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled + if (_firstRun && reset) + return; + + _firstRun = false; + + if (reset) { + opt = null; + setNotificationPosition(1); + return; + } + + setNotificationPosition(opt.NOTIFICATION_POSITION); +} + +function setNotificationPosition(position) { + switch (position) { + case 0: + Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.START; + Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.START; + break; + case 1: + Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.CENTER; + Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.START; + break; + case 2: + Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.END; + Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.START; + break; + case 3: + Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.START; + Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.END; + break; + case 4: + Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.CENTER; + Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.END; + break; + case 5: + Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.END; + Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.END; + break; + } +} diff --git a/extensions/vertical-workspaces/optionsFactory.js b/extensions/vertical-workspaces/lib/optionsFactory.js index bfaaec6..da62dd1 100644 --- a/extensions/vertical-workspaces/optionsFactory.js +++ b/extensions/vertical-workspaces/lib/optionsFactory.js @@ -1,5 +1,5 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * optionsFactory.js * * @author GdH <G-dH@github.com> @@ -13,16 +13,25 @@ const { Gtk, Gio, GObject } = imports.gi; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); -const Settings = Me.imports.settings; +const Settings = Me.imports.lib.settings; const shellVersion = Settings.shellVersion; // gettext const _ = Settings._; +const ProfileNames = [ + _('GNOME 3'), + _('GNOME 40+ - Bottom Hot Edge'), + _('Hot Corner Centric - Top Left Hot Corner'), + _('Dock Overview - Bottom Hot Edge'), +]; + // libadwaita is available starting with GNOME Shell 42. let Adw = null; -try { Adw = imports.gi.Adw; } catch (e) {} +try { + Adw = imports.gi.Adw; +} catch (e) {} function _newImageFromIconName(name) { return Gtk.Image.new_from_icon_name(name); @@ -35,7 +44,6 @@ var ItemFactory = class ItemFactory { } getRowWidget(text, caption, widget, variable, options = []) { - let item = []; let label; if (widget) { @@ -55,9 +63,9 @@ var ItemFactory = class ItemFactory { const captionLabel = new Gtk.Label({ halign: Gtk.Align.START, wrap: true, - /*width_chars: 80,*/ - xalign: 0 - }) + /* width_chars: 80, */ + xalign: 0, + }); const context = captionLabel.get_style_context(); context.add_class('dim-label'); context.add_class('caption'); @@ -79,25 +87,24 @@ var ItemFactory = class ItemFactory { } if (widget) { - if (widget._is_switch) { + if (widget._isSwitch) this._connectSwitch(widget, key, variable); - } else if (widget._is_spinbutton || widget._is_scale) { + else if (widget._isSpinButton || widget._isScale) this._connectSpinButton(widget, key, variable); - } else if (widget._is_combo_box) { + else if (widget._isComboBox) this._connectComboBox(widget, key, variable, options); - } else if (widget._is_drop_down) { + else if (widget._isDropDown) this._connectDropDown(widget, key, variable, options); - } } return item; } - _connectSwitch(widget, key, variable) { + _connectSwitch(widget, key /* , variable */) { this._settings.bind(key, widget, 'active', Gio.SettingsBindFlags.DEFAULT); } - _connectSpinButton(widget, key, variable) { + _connectSpinButton(widget, key /* , variable */) { this._settings.bind(key, widget.adjustment, 'value', Gio.SettingsBindFlags.DEFAULT); } @@ -108,9 +115,9 @@ var ItemFactory = class ItemFactory { for (const [label, value] of options) { let iter; model.set(iter = model.append(), [0, 1], [label, value]); - if (value === currentValue) { + if (value === currentValue) widget.set_active_iter(iter); - } + widget._comboMap[value] = iter; } this._gOptions.connect(`changed::${key}`, () => { @@ -119,7 +126,8 @@ var ItemFactory = class ItemFactory { widget.connect('changed', () => { const [success, iter] = widget.get_active_iter(); - if (!success) return; + if (!success) + return; this._gOptions.set(variable, model.get_value(iter, 1)); }); @@ -132,23 +140,22 @@ var ItemFactory = class ItemFactory { const text = options[i][0]; const id = options[i][1]; model.append(new DropDownItem({ text, id })); - if (id === currentValue) { + if (id === currentValue) widget.set_selected(i); - } } const factory = new Gtk.SignalListItemFactory(); - factory.connect("setup", (factory, list_item) => { - const label = new Gtk.Label({xalign: 0}); - list_item.set_child(label); + factory.connect('setup', (fact, listItem) => { + const label = new Gtk.Label({ xalign: 0 }); + listItem.set_child(label); }); - factory.connect("bind", (factory, list_item) => { - const label = list_item.get_child(); - const item = list_item.get_item(); + factory.connect('bind', (fact, listItem) => { + const label = listItem.get_child(); + const item = listItem.get_item(); label.set_text(item.text); }); - widget.connect("notify::selected-item", (dropDown) => { + widget.connect('notify::selected-item', dropDown => { const item = dropDown.get_selected_item(); this._gOptions.set(variable, item.id); }); @@ -157,9 +164,8 @@ var ItemFactory = class ItemFactory { const newId = this._gOptions.get(variable, true); for (let i = 0; i < options.length; i++) { const id = options[i][1]; - if (id === newId) { + if (id === newId) widget.set_selected(i); - } } }); @@ -172,7 +178,7 @@ var ItemFactory = class ItemFactory { valign: Gtk.Align.CENTER, hexpand: true, }); - sw._is_switch = true; + sw._isSwitch = true; return sw; } @@ -185,7 +191,7 @@ var ItemFactory = class ItemFactory { xalign: 0.5, }); spinButton.set_adjustment(adjustment); - spinButton._is_spinbutton = true; + spinButton._isSpinButton = true; return spinButton; } @@ -201,20 +207,20 @@ var ItemFactory = class ItemFactory { const renderer = new Gtk.CellRendererText(); comboBox.pack_start(renderer, true); comboBox.add_attribute(renderer, 'text', 0); - comboBox._is_combo_box = true; + comboBox._isComboBox = true; return comboBox; } newDropDown() { const dropDown = new Gtk.DropDown({ model: new Gio.ListStore({ - item_type: DropDownItem + item_type: DropDownItem, }), halign: Gtk.Align.END, valign: Gtk.Align.CENTER, hexpand: true, }); - dropDown._is_drop_down = true; + dropDown._isDropDown = true; return dropDown; } @@ -232,7 +238,7 @@ var ItemFactory = class ItemFactory { }); scale.set_size_request(300, -1); scale.set_adjustment(adjustment); - scale._is_scale = true; + scale._isScale = true; return scale; } @@ -258,13 +264,95 @@ var ItemFactory = class ItemFactory { return linkBtn; } - newResetButton(callback) { + newButton() { const btn = new Gtk.Button({ halign: Gtk.Align.END, valign: Gtk.Align.CENTER, hexpand: true, + }); + + btn._activatable = true; + return btn; + } + + newPresetButton(opt, profileIndex) { + const load = opt.loadProfile.bind(opt); + const save = opt.storeProfile.bind(opt); + const reset = opt.resetProfile.bind(opt); + + const box = new Gtk.Box({ + halign: Gtk.Align.END, + valign: Gtk.Align.CENTER, + hexpand: true, + spacing: 8, + }); + box.is_profile_box = true; + + const entry = new Gtk.Entry({ + width_chars: 40, + halign: Gtk.Align.END, + valign: Gtk.Align.CENTER, + hexpand: true, + xalign: 0, + }); + entry.set_text(opt.get(`profileName${profileIndex}`)); + entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, 'edit-clear-symbolic'); + entry.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, true); + entry.connect('icon-press', e => e.set_text('')); + entry.connect('changed', e => opt.set(`profileName${profileIndex}`, e.get_text())); + + const resetProfile = this.newButton(); + resetProfile.set({ + tooltip_text: _('Reset profile to defaults'), + icon_name: 'edit-delete-symbolic', + hexpand: false, css_classes: ['destructive-action'], - icon_name: 'view-refresh-symbolic' + }); + + function setName() { + let name = opt.get(`profileName${profileIndex}`, true); + if (!name) + name = ProfileNames[profileIndex - 1]; + entry.set_text(name); + } + + setName(); + resetProfile.connect('clicked', () => { + reset(profileIndex); + setName(); + }); + resetProfile._activatable = false; + + const loadProfile = this.newButton(); + loadProfile.set({ + tooltip_text: _('Load profile'), + icon_name: 'view-refresh-symbolic', + hexpand: false, + }); + loadProfile.connect('clicked', () => load(profileIndex)); + loadProfile._activatable = false; + + const saveProfile = this.newButton(); + saveProfile.set({ + tooltip_text: _('Save current settings into this profile'), + icon_name: 'document-save-symbolic', + hexpand: false, + }); + saveProfile.connect('clicked', () => save(profileIndex)); + saveProfile._activatable = false; + + box.append(resetProfile); + box.append(entry); + box.append(saveProfile); + box.append(loadProfile); + return box; + } + + newResetButton(callback) { + const btn = this.newButton(); + btn.set({ + css_classes: ['destructive-action'], + icon_name: 'edit-delete-symbolic', }); btn.connect('clicked', callback); @@ -278,7 +366,7 @@ var ItemFactory = class ItemFactory { valign: Gtk.Align.CENTER, hexpand: true, css_classes: ['destructive-action'], - icon_name: 'view-refresh-symbolic' + icon_name: 'edit-delete-symbolic', }); btn.connect('clicked', () => { @@ -290,7 +378,7 @@ var ItemFactory = class ItemFactory { btn._activatable = false; return btn; } -} +}; var AdwPrefs = class { constructor(gOptions) { @@ -300,13 +388,13 @@ var AdwPrefs = class { getFilledWindow(window, pages) { for (let page of pages) { const title = page.title; - const icon_name = page.iconName; + const iconName = page.iconName; const optionList = page.optionList; window.add( this._getAdwPage(optionList, { title, - icon_name + icon_name: iconName, }) ); } @@ -326,13 +414,13 @@ var AdwPrefs = class { const option = item[0]; const widget = item[1]; if (!widget) { - if (group) { + if (group) page.add(group); - } + group = new Adw.PreferencesGroup({ title: option, hexpand: true, - width_request: 700 + width_request: 700, }); continue; } @@ -349,25 +437,25 @@ var AdwPrefs = class { margin_top: 8, margin_bottom: 8, hexpand: true, - }) - /*for (let i of item) { + }); + /* for (let i of item) { box.append(i);*/ grid.attach(option, 0, 0, 1, 1); - if (widget) { + if (widget) grid.attach(widget, 1, 0, 1, 1); - } + row.set_child(grid); - if (widget._activatable === false) { + if (widget._activatable === false) row.activatable = false; - } else { + else row.activatable_widget = widget; - } + group.add(row); } page.add(group); return page; } -} +}; var LegacyPrefs = class { constructor(gOptions) { @@ -376,14 +464,14 @@ var LegacyPrefs = class { getPrefsWidget(pages) { const prefsWidget = new Gtk.Box({ - orientation: Gtk.Orientation.VERTICAL + orientation: Gtk.Orientation.VERTICAL, }); const stack = new Gtk.Stack({ - hexpand: true + hexpand: true, }); const stackSwitcher = new Gtk.StackSwitcher({ halign: Gtk.Align.CENTER, - hexpand: true + hexpand: true, }); const context = stackSwitcher.get_style_context(); @@ -398,7 +486,7 @@ var LegacyPrefs = class { vscrollbar_policy: Gtk.PolicyType.AUTOMATIC, vexpand: true, hexpand: true, - visible: true + visible: true, }; const pagesBtns = []; @@ -411,13 +499,13 @@ var LegacyPrefs = class { stack.add_named(this._getLegacyPage(optionList, pageProperties), name); pagesBtns.push( - [new Gtk.Label({ label: title}), _newImageFromIconName(iconName, Gtk.IconSize.BUTTON)] + [new Gtk.Label({ label: title }), _newImageFromIconName(iconName, Gtk.IconSize.BUTTON)] ); } let stBtn = stackSwitcher.get_first_child ? stackSwitcher.get_first_child() : null; for (let i = 0; i < pagesBtns.length; i++) { - const box = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL, spacing: 6, visible: true}); + const box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, spacing: 6, visible: true }); const icon = pagesBtns[i][1]; icon.margin_start = 30; icon.margin_end = 30; @@ -433,12 +521,15 @@ var LegacyPrefs = class { } } - stack.show_all && stack.show_all(); - stackSwitcher.show_all && stackSwitcher.show_all(); + if (stack.show_all) + stack.show_all(); + if (stackSwitcher.show_all) + stackSwitcher.show_all(); prefsWidget.append(stack); - prefsWidget.show_all && prefsWidget.show_all(); + if (prefsWidget.show_all) + prefsWidget.show_all(); prefsWidget._stackSwitcher = stackSwitcher; @@ -457,7 +548,7 @@ var LegacyPrefs = class { margin_bottom: 12, }); - const context = page.get_style_context(); + let context = page.get_style_context(); context.add_class('background'); let frame; @@ -472,20 +563,20 @@ var LegacyPrefs = class { const lbl = new Gtk.Label({ label: option, xalign: 0, - margin_bottom: 4 + margin_bottom: 4, }); - const context = lbl.get_style_context(); + context = lbl.get_style_context(); context.add_class('heading'); mainBox.append(lbl); frame = new Gtk.Frame({ - margin_bottom: 16 + margin_bottom: 16, }); frameBox = new Gtk.ListBox({ - selection_mode: null + selection_mode: null, }); mainBox.append(frame); @@ -500,21 +591,21 @@ var LegacyPrefs = class { margin_end: 8, margin_top: 8, margin_bottom: 8, - hexpand: true - }) + hexpand: true, + }); grid.attach(option, 0, 0, 5, 1); - if (widget) { + if (widget) grid.attach(widget, 5, 0, 2, 1); - } + frameBox.append(grid); } page.set_child(mainBox); return page; } -} +}; const DropDownItem = GObject.registerClass({ GTypeName: 'DropdownItem', @@ -538,6 +629,7 @@ const DropDownItem = GObject.registerClass({ get text() { return this._text; } + set text(text) { this._text = text; } @@ -545,6 +637,7 @@ const DropDownItem = GObject.registerClass({ get id() { return this._id; } + set id(id) { this._id = id; } diff --git a/extensions/vertical-workspaces/lib/osdWindow.js b/extensions/vertical-workspaces/lib/osdWindow.js new file mode 100644 index 0000000..a010558 --- /dev/null +++ b/extensions/vertical-workspaces/lib/osdWindow.js @@ -0,0 +1,93 @@ +/** + * V-Shell (Vertical Workspaces) + * osdWindow.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; + +const { Clutter } = imports.gi; +const Main = imports.ui.main; +const OsdWindow = imports.ui.osdWindow; + +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const _Util = Me.imports.lib.util; + +const OsdPositions = { + 1: { + x_align: Clutter.ActorAlign.START, + y_align: Clutter.ActorAlign.START, + }, + 2: { + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.START, + }, + 3: { + x_align: Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.START, + }, + 4: { + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, + }, + 5: { + x_align: Clutter.ActorAlign.START, + y_align: Clutter.ActorAlign.END, + }, + 6: { + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.END, + }, + 7: { + x_align: Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.END, + }, +}; + +let _overrides; +let opt; +let _firstRun = true; + +function update(reset = false) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('osdWindowModule', true); + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled + if (_firstRun && reset) + return; + + _firstRun = false; + + if (_overrides) + _overrides.removeAll(); + + if (reset || !moduleEnabled) { + updateExistingOsdWindows(6); + _overrides = null; + opt = null; + return; + } + + _overrides = new _Util.Overrides(); + _overrides.addOverride('osdWindow', OsdWindow.OsdWindow.prototype, OsdWindowCommon); +} + +function updateExistingOsdWindows(position) { + position = position ? position : opt.OSD_POSITION; + Main.osdWindowManager._osdWindows.forEach(osd => { + osd.set(OsdPositions[position]); + }); +} + +const OsdWindowCommon = { + after_show() { + if (!opt.OSD_POSITION) + this.opacity = 0; + this.set(OsdPositions[opt.OSD_POSITION]); + }, +}; diff --git a/extensions/vertical-workspaces/lib/overlayKey.js b/extensions/vertical-workspaces/lib/overlayKey.js new file mode 100644 index 0000000..e0fc11d --- /dev/null +++ b/extensions/vertical-workspaces/lib/overlayKey.js @@ -0,0 +1,108 @@ +/** + * V-Shell (Vertical Workspaces) + * overlayKey.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; +const { GObject, Gio, GLib, Meta, St } = imports.gi; + +const Main = imports.ui.main; +const Overview = imports.ui.overview; + +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const _Util = Me.imports.lib.util; + +const _ = Me.imports.lib.settings._; +const shellVersion = _Util.shellVersion; +const WIN_SEARCH_PREFIX = Me.imports.lib.windowSearchProvider.prefix; +const RECENT_FILES_PREFIX = Me.imports.lib.recentFilesSearchProvider.prefix; +const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard'; + +let opt; +let _firstRun = true; + +let _originalOverlayKeyHandlerId; +let _overlayKeyHandlerId; + +function update(reset = false) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('overlayKeyModule', true); + reset = reset || (!_firstRun && !moduleEnabled); + + // don't even touch this module if disabled + if (_firstRun && !moduleEnabled) + return; + + _firstRun = false; + + if (reset) { + _updateOverlayKey(reset); + opt = null; + return; + } + + _updateOverlayKey(); +} + +function _updateOverlayKey(reset = false) { + if (reset) { + _restoreOverlayKeyHandler(); + } else if (!_originalOverlayKeyHandlerId) { + _originalOverlayKeyHandlerId = GObject.signal_handler_find(global.display, { signalId: 'overlay-key' }); + if (_originalOverlayKeyHandlerId !== null) + global.display.block_signal_handler(_originalOverlayKeyHandlerId); + _connectOverlayKey.bind(Main.overview._overview.controls)(); + } +} + +function _restoreOverlayKeyHandler() { + // Disconnect modified overlay key handler + if (_overlayKeyHandlerId !== null) { + global.display.disconnect(_overlayKeyHandlerId); + _overlayKeyHandlerId = null; + } + + // Unblock original overlay key handler + if (_originalOverlayKeyHandlerId !== null) { + global.display.unblock_signal_handler(_originalOverlayKeyHandlerId); + _originalOverlayKeyHandlerId = null; + } +} + +function _connectOverlayKey() { + this._a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA }); + + this._lastOverlayKeyTime = 0; + _overlayKeyHandlerId = global.display.connect('overlay-key', () => { + if (this._a11ySettings.get_boolean('stickykeys-enable')) + return; + + const { initialState, finalState, transitioning } = + this._stateAdjustment.getStateTransitionParams(); + + const time = GLib.get_monotonic_time() / 1000; + const timeDiff = time - this._lastOverlayKeyTime; + this._lastOverlayKeyTime = time; + + const shouldShift = St.Settings.get().enable_animations + ? transitioning && finalState > initialState + : Main.overview.visible && timeDiff < Overview.ANIMATION_TIME; + + const mode = opt.OVERLAY_KEY_SECONDARY; + if (shouldShift) { + if (mode === 1) + this._shiftState(Meta.MotionDirection.UP); + else if (mode === 2) + _Util.activateSearchProvider(WIN_SEARCH_PREFIX); + else if (mode === 3) + _Util.activateSearchProvider(RECENT_FILES_PREFIX); + } else { + Main.overview.toggle(); + } + }); +} diff --git a/extensions/vertical-workspaces/overview.js b/extensions/vertical-workspaces/lib/overview.js index db267b6..2f23d05 100644 --- a/extensions/vertical-workspaces/overview.js +++ b/extensions/vertical-workspaces/lib/overview.js @@ -1,9 +1,6 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * overview.js - * - * panel barrier should follow panel position - * or disable it to not collide with Custom Hot Corners barriers * * @author GdH <G-dH@github.com> * @copyright 2022 - 2023 @@ -13,21 +10,18 @@ 'use strict'; -const { Meta, Clutter } = imports.gi; - -const Main = imports.ui.main; const Overview = imports.ui.overview; const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.util; +const _Util = Me.imports.lib.util; let _overrides; let opt; function update(reset = false) { - if (_overrides) { + if (_overrides) _overrides.removeAll(); - } + if (reset) { _overrides = null; @@ -35,34 +29,31 @@ function update(reset = false) { return; } - opt = Me.imports.settings.opt; - + opt = Me.imports.lib.settings.opt; _overrides = new _Util.Overrides(); _overrides.addOverride('Overview', Overview.Overview.prototype, OverviewCommon); } - const OverviewCommon = { - _showDone: function() { + _showDone() { this._animationInProgress = false; this._coverPane.hide(); - + this.emit('shown'); // Handle any calls to hide* while we were showing if (!this._shown) this._animateNotVisible(); - + this._syncGrab(); // if user activates overview during startup animation, transition needs to be shifted to the state 2 here const controls = this._overview._controls; if (controls._searchController._searchActive && controls._stateAdjustment.value === 1) { - if (opt.SEARCH_VIEW_ANIMATION) { + if (opt.SEARCH_VIEW_ANIMATION) controls._onSearchChanged(); - } else { + else if (!opt.OVERVIEW_MODE2) controls._stateAdjustment.value = 2; - } } - } -} + }, +}; diff --git a/extensions/vertical-workspaces/overviewControls.js b/extensions/vertical-workspaces/lib/overviewControls.js index ffe0f89..4959b83 100644 --- a/extensions/vertical-workspaces/overviewControls.js +++ b/extensions/vertical-workspaces/lib/overviewControls.js @@ -1,7 +1,7 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * overviewControls.js - * + * * @author GdH <G-dH@github.com> * @copyright 2022 - 2023 * @license GPL-3.0 @@ -10,7 +10,7 @@ 'use strict'; -const { Clutter, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi; +const { Clutter, GLib, GObject, St } = imports.gi; const Main = imports.ui.main; const Util = imports.misc.util; const OverviewControls = imports.ui.overviewControls; @@ -22,55 +22,50 @@ const FitMode = imports.ui.workspacesView.FitMode; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); -const _Util = Me.imports.util; +const _Util = Me.imports.lib.util; + let _overrides; +let opt; const ANIMATION_TIME = imports.ui.overview.ANIMATION_TIME; -const DASH_MAX_SIZE_RATIO = 0.15; +const DASH_MAX_SIZE_RATIO = 0.25; let _originalSearchControllerSigId; let _searchControllerSigId; -let _startupAnimTimeoutId1; -let _startupAnimTimeoutId2; -let _updateAppGridTimeoutId; +let _timeouts; let _startupInitComplete = false; -let opt; - - function update(reset = false) { - - if (_overrides) { + if (_overrides) _overrides.removeAll(); + + if (_timeouts) { + Object.values(_timeouts).forEach(id => { + if (id) + GLib.source_remove(id); + }); } - + _replaceOnSearchChanged(reset); - + if (reset) { _overrides = null; opt = null; + _timeouts = null; return; } - - opt = Me.imports.settings.opt; + _timeouts = {}; + + opt = Me.imports.lib.settings.opt; _overrides = new _Util.Overrides(); _overrides.addOverride('ControlsManager', OverviewControls.ControlsManager.prototype, ControlsManager); - - if (opt.ORIENTATION === Clutter.Orientation.VERTICAL) { + + if (opt.ORIENTATION === Clutter.Orientation.VERTICAL) _overrides.addOverride('ControlsManagerLayout', OverviewControls.ControlsManagerLayout.prototype, ControlsManagerLayoutVertical); - } else { + else _overrides.addOverride('ControlsManagerLayout', OverviewControls.ControlsManagerLayout.prototype, ControlsManagerLayoutHorizontal); - } -} - -function _dashNotDefault() { - return Main.overview.dash !== Main.overview._overview._controls.layoutManager._dash; -} - -function _dashIsDashToDock() { - return Main.overview.dash._isHorizontal !== undefined; } function _replaceOnSearchChanged(reset = false) { @@ -84,29 +79,32 @@ function _replaceOnSearchChanged(reset = false) { searchController.unblock_signal_handler(_originalSearchControllerSigId); _originalSearchControllerSigId = 0; } + Main.overview._overview._controls.layoutManager._searchController._searchResults.translation_x = 0; + Main.overview._overview._controls.layoutManager._searchController._searchResults.translation_y = 0; + Main.overview.searchEntry.visible = true; + Main.overview.searchEntry.opacity = 255; } else { // reconnect signal to use custom function (callbacks cannot be overridden in class prototype, they are already in memory as a copy for the given callback) _originalSearchControllerSigId = GObject.signal_handler_find(searchController, { signalId: 'notify', detail: 'search-active' }); - if (_originalSearchControllerSigId) { + if (_originalSearchControllerSigId) searchController.block_signal_handler(_originalSearchControllerSigId); - } + _searchControllerSigId = searchController.connect('notify::search-active', ControlsManager._onSearchChanged.bind(Main.overview._overview.controls)); } - } -var ControlsManager = { +const ControlsManager = { // this function is used as a callback by a signal handler, needs to be reconnected after modification as the original callback uses a copy of the original function - /*_update: function() { + /* _update: function() { ... }*/ // this function has duplicate in WorkspaceView so we use one function for both to avoid issues with syncing them - _getFitModeForState: function(state) { + _getFitModeForState(state) { return _getFitModeForState(state); }, - _updateThumbnailsBox: function() { + _updateThumbnailsBox() { const { shouldShow } = this._thumbnailsBox; const thumbnailsBoxVisible = shouldShow; this._thumbnailsBox.visible = thumbnailsBoxVisible; @@ -116,13 +114,12 @@ var ControlsManager = { }, // this function is pure addition to the original code and handles wsDisp transition to APP_GRID view - _updateWorkspacesDisplay: function() { + _updateWorkspacesDisplay() { this._workspacesDisplay.translation_x = 0; this._workspacesDisplay.translation_y = 0; this._workspacesDisplay.scale_x = 1; this._workspacesDisplay.scale_y = 1; const { initialState, finalState, progress, currentState } = this._stateAdjustment.getStateTransitionParams(); - const { searchActive } = this._searchController; const paramsForState = s => { let opacity; @@ -146,23 +143,26 @@ var ControlsManager = { let opacity = Math.round(Util.lerp(initialParams.opacity, finalParams.opacity, progress)); - let workspacesDisplayVisible = (opacity != 0)/* && !(searchActive)*/; + let workspacesDisplayVisible = opacity !== 0/* && !(searchActive)*/; // improve transition from search results to desktop - if (finalState === 0 && this._searchController._searchResults.visible) { + if (finalState === 0 && this._searchController._searchResults.visible) this._searchController.hide(); - } // reset Static Workspace window picker mode - if (currentState === 0/*finalState === 0 && progress === 1*/ && opt.OVERVIEW_MODE && opt.WORKSPACE_MODE) { + if (currentState === 0/* finalState === 0 && progress === 1*/ && opt.OVERVIEW_MODE && opt.WORKSPACE_MODE) opt.WORKSPACE_MODE = 0; - } + + if (currentState < 2 && currentState > 1) + WorkspaceThumbnail.RESCALE_ANIMATION_TIME = 0; + else + WorkspaceThumbnail.RESCALE_ANIMATION_TIME = 200; if (!opt.WS_ANIMATION || !opt.SHOW_WS_TMB) { this._workspacesDisplay.opacity = opacity; } else if (!opt.SHOW_WS_TMB_BG) { // fade out ws wallpaper during transition to ws switcher if ws switcher background disabled - const ws = this._workspacesDisplay._workspacesViews[global.display.get_primary_monitor()]._workspaces[this._workspaceAdjustment.value]; + const ws = this._workspacesDisplay._workspacesViews[global.display.get_primary_monitor()]?._workspaces[this._workspaceAdjustment.value]; if (ws) ws._background.opacity = opacity; } @@ -172,19 +172,19 @@ var ControlsManager = { const dash = this.dash; const searchEntryBin = this._searchEntryBin; // this dash transition collides with startup animation and freezes GS for good, needs to be delayed (first Main.overview 'hiding' event enables it) - const skipDash = _dashNotDefault(); + const skipDash = _Util.dashNotDefault(); // OVERVIEW_MODE 2 should animate dash and wsTmbBox only if WORKSPACE_MODE === 0 (windows not spread) const animateOverviewMode2 = opt.OVERVIEW_MODE2 && !(finalState === 1 && opt.WORKSPACE_MODE); - if (!Main.layoutManager._startingUp && ((!opt.SHOW_WS_PREVIEW_BG && !(opt.OVERVIEW_MODE2)) || animateOverviewMode2)) { + if (!Main.layoutManager._startingUp && ((!opt.SHOW_WS_PREVIEW_BG && !opt.OVERVIEW_MODE2) || animateOverviewMode2)) { if (!tmbBox._translationOriginal || Math.abs(tmbBox._translationOriginal[0]) > 500) { // swipe gesture can call this calculation before tmbBox is finalized, giving nonsense width - const [tmbTranslation_x, tmbTranslation_y, dashTranslation_x, dashTranslation_y, searchTranslation_y] = _Util.getOverviewTranslations(opt, dash, tmbBox, searchEntryBin); - tmbBox._translationOriginal = [tmbTranslation_x, tmbTranslation_y]; - dash._translationOriginal = [dashTranslation_x, dashTranslation_y]; - searchEntryBin._translationOriginal = searchTranslation_y; + const [tmbTranslationX, tmbTranslationY, dashTranslationX, dashTranslationY, searchTranslationY] = _Util.getOverviewTranslations(opt, dash, tmbBox, searchEntryBin); + tmbBox._translationOriginal = [tmbTranslationX, tmbTranslationY]; + dash._translationOriginal = [dashTranslationX, dashTranslationY]; + searchEntryBin._translationOriginal = searchTranslationY; } if (finalState === 0 || initialState === 0) { - const prg = Math.abs((finalState == 0 ? 0 : 1) - progress); + const prg = Math.abs((finalState === 0 ? 0 : 1) - progress); tmbBox.translation_x = Math.round(prg * tmbBox._translationOriginal[0]); tmbBox.translation_y = Math.round(prg * tmbBox._translationOriginal[1]); if (!skipDash) { @@ -195,9 +195,9 @@ var ControlsManager = { } if (progress === 1) { tmbBox._translationOriginal = 0; - if (!skipDash) { + if (!skipDash) dash._translationOriginal = 0; - } + searchEntryBin._translationOriginal = 0; } } else if (!Main.layoutManager._startingUp && (tmbBox.translation_x || tmbBox.translation_y)) { @@ -211,11 +211,10 @@ var ControlsManager = { } if (!Main.layoutManager._startingUp) { - if (initialState === ControlsState.HIDDEN && finalState === ControlsState.APP_GRID) { + if (initialState === ControlsState.HIDDEN && finalState === ControlsState.APP_GRID) this._appDisplay.opacity = Math.round(progress * 255); - } else { + else this._appDisplay.opacity = 255 - opacity; - } } if (currentState === ControlsState.APP_GRID) { @@ -230,22 +229,19 @@ var ControlsManager = { // set searchEntry above appDisplay this.set_child_above_sibling(this._searchEntryBin, null); // move dash above wsTmb for case that dash and wsTmb animate from the same side - this.set_child_above_sibling(dash, null); + if (!_Util.dashNotDefault()) + this.set_child_above_sibling(dash, null); this.set_child_below_sibling(this._thumbnailsBox, null); this.set_child_below_sibling(this._workspacesDisplay, null); this.set_child_below_sibling(this._appDisplay, null); } else if (!this.dash._isAbove && progress === 1 && finalState > ControlsState.HIDDEN) { // set dash above workspace in the overview - if (!_dashNotDefault()) { - this.set_child_above_sibling(this._thumbnailsBox, null); - this.set_child_above_sibling(this._searchEntryBin, null); + this.set_child_above_sibling(this._thumbnailsBox, null); + this.set_child_above_sibling(this._searchEntryBin, null); + if (!_Util.dashNotDefault()) this.set_child_above_sibling(this.dash, null); - this.dash._isAbove = true; - } - - // update max tmb scale in case some other extension changed it - WorkspaceThumbnail.MAX_THUMBNAIL_SCALE = opt.MAX_THUMBNAIL_SCALE; + this.dash._isAbove = true; } else if (this.dash._isAbove && progress < 1) { // keep dash below for ws transition between the overview and hidden state this.set_child_above_sibling(this._workspacesDisplay, null); @@ -254,7 +250,7 @@ var ControlsManager = { }, // fix for upstream bug - appGrid.visible after transition from APP_GRID to HIDDEN - _updateAppDisplayVisibility: function(stateTransitionParams = null) { + _updateAppDisplayVisibility(stateTransitionParams = null) { if (!stateTransitionParams) stateTransitionParams = this._stateAdjustment.getStateTransitionParams(); @@ -262,6 +258,12 @@ var ControlsManager = { if (this.dash.showAppsButton.checked) this._searchTransition = false; + // update App Grid after settings changed + // only if the App Grid is currently visible on the screen, the paging updates correctly + if (currentState === ControlsState.APP_GRID && this._appDisplay.visible && opt._appGridNeedsRedisplay) { + Me.imports.lib.appDisplay._updateAppGridProperties(); + opt._appGridNeedsRedisplay = false; + } // if !APP_GRID_ANIMATION, appGrid needs to be hidden in WINDOW_PICKER mode (1) // but needs to be visible for transition from HIDDEN (0) to APP_GRID (2) this._appDisplay.visible = @@ -271,150 +273,156 @@ var ControlsManager = { !this._searchTransition; }, - _onSearchChanged: function() { - // if user start typing or activated search provider during overview animation, this switcher will be called again after animation ends - if (opt.SEARCH_VIEW_ANIMATION && Main.overview._animationInProgress) return; - + _onSearchChanged() { const { finalState, currentState } = this._stateAdjustment.getStateTransitionParams(); + const { searchActive } = this._searchController; const SIDE_CONTROLS_ANIMATION_TIME = 250; // OverviewControls.SIDE_CONTROLS_ANIMATION_TIME = Overview.ANIMATION_TIME = 250 + const entry = this._searchEntry; + if (opt.SHOW_SEARCH_ENTRY) { + entry.visible = true; + entry.opacity = 255; + } else if (!(searchActive && entry.visible)) { + entry.visible = true; + entry.opacity = searchActive ? 0 : 255; + // show search entry only if the user starts typing, and hide it when leaving the search mode + entry.ease({ + opacity: searchActive ? 255 : 0, + duration: SIDE_CONTROLS_ANIMATION_TIME / 2, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + entry.visible = searchActive; + }, + }); + } + + // if user start typing or activated search provider during overview animation, this switcher will be called again after animation ends + if (opt.SEARCH_VIEW_ANIMATION && Main.overview._animationInProgress && finalState !== ControlsState.HIDDEN) + return; + if (!searchActive) { this._workspacesDisplay.reactive = true; this._workspacesDisplay.setPrimaryWorkspaceVisible(true); } else { this._searchController.show(); + entry.visible = true; + entry.opacity = 255; } this._searchTransition = true; this._searchController._searchResults.translation_x = 0; this._searchController._searchResults.translation_y = 0; + this._searchController.opacity = 255; this._searchController.visible = true; - if (opt.SEARCH_VIEW_ANIMATION && !this.dash.showAppsButton.checked && ![4, 8].includes(opt.WS_TMB_POSITION) /*&& !opt.OVERVIEW_MODE2*/) { + if (opt.SEARCH_VIEW_ANIMATION && !this.dash.showAppsButton.checked && ![4, 8].includes(opt.WS_TMB_POSITION) /* && !opt.OVERVIEW_MODE2*/) { this._updateAppDisplayVisibility(); this._searchController.opacity = searchActive ? 255 : 0; - let translation_x = 0; - let translation_y = 0; + let translationX = 0; + let translationY = 0; const geometry = global.display.get_monitor_geometry(global.display.get_primary_monitor()); - if (currentState < ControlsState.APP_GRID) { switch (opt.SEARCH_VIEW_ANIMATION) { - case 0: - translation_x = 0; - translation_y = 0; - break; - case 1: - // make it longer to cover the delay before results appears - translation_x = geometry.x + geometry.width - this._searchController.x + this._workspacesDisplay.width; - translation_y = 0; - break; - case 2: - translation_x = - this._searchController.x - 2 * this._workspacesDisplay.width; - translation_y = 0; - break; - case 3: - translation_x = 0; - translation_y = geometry.y + geometry.height + this._searchController.y + this._workspacesDisplay.height; - break; - case 5: - translation_x = 0; - translation_y = - this._searchController.y - 2 * this._workspacesDisplay.height; - break; + case 1: + // make it longer to cover the delay before results appears + translationX = geometry.width; + translationY = 0; + break; + case 2: + translationX = -geometry.width; + translationY = 0; + break; + case 3: + translationX = 0; + translationY = geometry.height; + break; + case 5: + translationX = 0; + translationY = -geometry.height; + break; } } if (searchActive) { - this._searchController._searchResults.translation_x = translation_x; - this._searchController._searchResults.translation_y = translation_y; + this._searchController._searchResults.translation_x = translationX; + this._searchController._searchResults.translation_y = translationY; } else { this._searchController._searchResults.translation_x = 0; this._searchController._searchResults.translation_y = 0; } this._searchController._searchResults.ease({ - //opacity: searchActive ? 255 : 0, - translation_x: searchActive ? 0 : translation_x, - translation_y: searchActive ? 0 : translation_y, + translation_x: searchActive ? 0 : translationX, + translation_y: searchActive ? 0 : translationY, duration: SIDE_CONTROLS_ANIMATION_TIME, mode: Clutter.AnimationMode.EASE_OUT_QUAD, onComplete: () => { this._searchController.visible = searchActive; this._searchTransition = false; - } + }, }); this._workspacesDisplay.opacity = 255; } else { this._appDisplay.ease({ - opacity: (searchActive || currentState < 2) ? 0 : 255, + opacity: searchActive || currentState < 2 ? 0 : 255, duration: SIDE_CONTROLS_ANIMATION_TIME / 2, mode: Clutter.AnimationMode.EASE_OUT_QUAD, onComplete: () => this._updateAppDisplayVisibility(), }); - //this._updateAppDisplayVisibility(); + // this._updateAppDisplayVisibility(); this._workspacesDisplay.setPrimaryWorkspaceVisible(true); - this._workspacesDisplay.ease({ + /* this._workspacesDisplay.ease({ opacity: searchActive ? 0 : 255, - duration: SIDE_CONTROLS_ANIMATION_TIME / 2, + duration: searchActive ? SIDE_CONTROLS_ANIMATION_TIME / 2 : 0, mode: Clutter.AnimationMode.EASE_OUT_QUAD, onComplete: () => { this._workspacesDisplay.reactive = !searchActive; this._workspacesDisplay.setPrimaryWorkspaceVisible(!searchActive); }, - }); + });*/ + this._searchController.opacity = searchActive ? 0 : 255; this._searchController.ease({ opacity: searchActive ? 255 : 0, - duration: SIDE_CONTROLS_ANIMATION_TIME / 2, + duration: searchActive ? SIDE_CONTROLS_ANIMATION_TIME * 2 : 0, mode: Clutter.AnimationMode.EASE_OUT_QUAD, onComplete: () => (this._searchController.visible = searchActive), }); } // reuse already tuned overview transition, just replace APP_GRID with the search view - if (!Main.overview._animationInProgress && finalState !== ControlsState.HIDDEN && !this.dash.showAppsButton.checked) { + if (!(opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) && !Main.overview._animationInProgress && finalState !== ControlsState.HIDDEN && !this.dash.showAppsButton.checked) { + Main.overview._overview._controls.layoutManager._searchController._searchResults._content.remove_style_class_name('search-section-content-om2'); + Main.overview.searchEntry.remove_style_class_name('search-entry-om2'); this._stateAdjustment.ease(searchActive ? ControlsState.APP_GRID : ControlsState.WINDOW_PICKER, { // shorter animation time when entering search view can avoid stuttering in transition // collecting search results take some time and the problematic part is the realization of the object on the screen // if the ws animation ends before this event, the whole transition is smoother // removing the ws transition (duration: 0) seems like the best solution here - duration: searchActive ? 0 : SIDE_CONTROLS_ANIMATION_TIME, + duration: searchActive || (opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE) ? 80 : SIDE_CONTROLS_ANIMATION_TIME, mode: Clutter.AnimationMode.EASE_OUT_QUAD, onComplete: () => { this._workspacesDisplay.setPrimaryWorkspaceVisible(!searchActive); - } - }); - } - - const entry = this._searchEntry; - if (opt.SHOW_SEARCH_ENTRY) { - entry.visible = true; - entry.opacity = 255; - } else { - entry.visible = true; - entry.opacity = searchActive ? 0 : 255; - // show search entry only if the user starts typing, and hide it when leaving the search mode - entry.ease({ - opacity: searchActive ? 255 : 0, - duration: SIDE_CONTROLS_ANIMATION_TIME / 2, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - onComplete: () => { - entry.visible = searchActive; }, }); + } else if (opt.OVERVIEW_MODE2 && !(opt.WORKSPACE_MODE || this.dash.showAppsButton.checked)) { + // add background to search results and make searchEntry border thicker for better visibility + Main.overview._overview._controls.layoutManager._searchController._searchResults._content.add_style_class_name('search-section-content-om2'); + Main.overview.searchEntry.add_style_class_name('search-entry-om2'); + } else { + Main.overview._overview._controls.layoutManager._searchController._searchResults._content.remove_style_class_name('search-section-content-om2'); + Main.overview.searchEntry.remove_style_class_name('search-entry-om2'); } }, - runStartupAnimation: async function(callback) { - // fix for upstream bug - overview always shows workspace 1 instead of the active one after restart - this._workspaceAdjustment.set_value(global.workspace_manager.get_active_workspace_index()); + async runStartupAnimation(callback) { this._ignoreShowAppsButtonToggle = true; - this._searchController.prepareToEnterOverview(); this._workspacesDisplay.prepareToEnterOverview(); @@ -443,7 +451,7 @@ var ControlsManager = { onComplete: () => { // part of the workaround for stuttering first app grid animation this._appDisplay.visible = true; - } + }, }); const dash = this.dash; @@ -457,10 +465,10 @@ var ControlsManager = { } const searchEntryBin = this._searchEntryBin; - const [tmbTranslation_x, tmbTranslation_y, dashTranslation_x, dashTranslation_y, searchTranslation_y] = + const [tmbTranslationX, tmbTranslationY, dashTranslationX, dashTranslationY, searchTranslationY] = _Util.getOverviewTranslations(opt, dash, tmbBox, searchEntryBin); - const onComplete = function() { + const onComplete = function () { // running init callback again causes issues (multiple connections) if (!_startupInitComplete) callback(); @@ -470,16 +478,24 @@ var ControlsManager = { this._appDisplay.opacity = 1; const [x, y] = this._appDisplay.get_position(); - const translation_x = - x; - const translation_y = - y; - this._appDisplay.translation_x = translation_x; - this._appDisplay.translation_y = translation_y; + const translationX = -x; + const translationY = -y; + this._appDisplay.translation_x = translationX; + this._appDisplay.translation_y = translationY; + GLib.idle_add(0, () => { + this._appDisplay._removeItem(this._appDisplay._orderedItems[0]); + this._appDisplay._redisplay(); + }); // let the main loop realize previous changes before continuing - _startupAnimTimeoutId1 = GLib.timeout_add( + _timeouts.startupAnim1 = GLib.timeout_add( GLib.PRIORITY_DEFAULT, 10, () => { + GLib.idle_add(0, () => { + this._appDisplay._removeItem(this._appDisplay._orderedItems[0]); + this._appDisplay._redisplay(); + }); this._appDisplay.translation_x = 0; this._appDisplay.translation_y = 0; this._appDisplay.visible = false; @@ -489,15 +505,15 @@ var ControlsManager = { this._appDisplay.opacity = 255; this.dash.showAppsButton.checked = true; } - _startupAnimTimeoutId1 = 0; + _timeouts.startupAnim1 = 0; return GLib.SOURCE_REMOVE; } ); }.bind(this); - if (dash.visible && !_dashNotDefault()) { - dash.translation_x = dashTranslation_x; - dash.translation_y = dashTranslation_y; + if (dash.visible && !_Util.dashNotDefault()) { + dash.translation_x = dashTranslationX; + dash.translation_y = dashTranslationY; dash.opacity = 255; dash.ease({ translation_x: 0, @@ -513,20 +529,20 @@ var ControlsManager = { // set dash opacity to make it visible if user enable it later dash.opacity = 255; // if dash is hidden, substitute the ease timeout with GLib.timeout - _startupAnimTimeoutId2 = GLib.timeout_add( + _timeouts.startupAnim2 = GLib.timeout_add( GLib.PRIORITY_DEFAULT, // delay + animation time - STARTUP_ANIMATION_TIME * 2 * opt.ANIMATION_TIME_FACTOR, + STARTUP_ANIMATION_TIME * 2 * St.Settings.get().slow_down_factor, () => { onComplete(); - _startupAnimTimeoutId2 = 0; + _timeouts.startupAnim2 = 0; return GLib.SOURCE_REMOVE; } ); } if (searchEntryBin.visible) { - searchEntryBin.translation_y = searchTranslation_y; + searchEntryBin.translation_y = searchTranslationY; searchEntryBin.ease({ translation_y: 0, delay: STARTUP_ANIMATION_TIME / 2, @@ -536,8 +552,8 @@ var ControlsManager = { } if (tmbBox.visible) { - tmbBox.translation_x = tmbTranslation_x; - tmbBox.translation_y = tmbTranslation_y; + tmbBox.translation_x = tmbTranslationX; + tmbBox.translation_y = tmbTranslationY; tmbBox.ease({ translation_x: 0, translation_y: 0, @@ -553,21 +569,21 @@ var ControlsManager = { if (workspacesViews.length > 1) { for (const view of workspacesViews) { if (view._monitorIndex !== global.display.get_primary_monitor() && view._thumbnails.visible) { - const tmbBox = view._thumbnails; - - _Util.getOverviewTranslations(opt, dash, tmbBox, searchEntryBin); - if (opt.SEC_WS_TMB_LEFT) { - tmbBox.translation_x = - (tmbBox.width + 12); // compensate for padding - } else if (opt.SEC_WS_TMB_RIGHT) { - tmbBox.translation_x = (tmbBox.width + 12); - } else if (opt.SEC_WS_TMB_TOP) { - tmbBox.translation_y = - (tmbBox.height + 12); - } else if (opt.SEC_WS_TMB_BOTTOM) { - tmbBox.translation_y = (tmbBox.height + 12); - } - tmbBox.opacity = 255; + const secTmbBox = view._thumbnails; + + _Util.getOverviewTranslations(opt, dash, secTmbBox, searchEntryBin); + if (opt.SEC_WS_TMB_LEFT) + secTmbBox.translation_x = -(secTmbBox.width + 12); // compensate for padding + else if (opt.SEC_WS_TMB_RIGHT) + secTmbBox.translation_x = secTmbBox.width + 12; + else if (opt.SEC_WS_TMB_TOP) + secTmbBox.translation_y = -(secTmbBox.height + 12); + else if (opt.SEC_WS_TMB_BOTTOM) + secTmbBox.translation_y = secTmbBox.height + 12; - tmbBox.ease({ + secTmbBox.opacity = 255; + + secTmbBox.ease({ translation_y: 0, delay: STARTUP_ANIMATION_TIME / 2, duration: STARTUP_ANIMATION_TIME, @@ -578,10 +594,7 @@ var ControlsManager = { } }, - animateToOverview: function(state, callback) { - // don't enter overview during updating appDisplay properties - if (_updateAppGridTimeoutId) - Main.overview.hide(); + animateToOverview(state, callback) { this._ignoreShowAppsButtonToggle = true; this._searchTransition = false; @@ -595,10 +608,13 @@ var ControlsManager = { // in which case the the animation is greatly delayed, stuttering, or even skipped // for user it is more acceptable to watch delayed smooth animation, // even if it takes little more time, than jumping frames - const delay = global.display.get_tab_list(0, global.workspace_manager.get_active_workspace()).length * 3; + let delay = 0; + if (opt.DELAY_OVERVIEW_ANIMATION) + delay = global.display.get_tab_list(0, global.workspace_manager.get_active_workspace()).length * 3; + this._stateAdjustment.ease(state, { delay, - duration: 250, //Overview.ANIMATION_TIME, + duration: 250, // Overview.ANIMATION_TIME, mode: Clutter.AnimationMode.EASE_OUT_QUAD, onStopped: () => { if (callback) @@ -611,29 +627,28 @@ var ControlsManager = { this._ignoreShowAppsButtonToggle = false; }, -} +}; -var ControlsManagerLayoutVertical = { - _computeWorkspacesBoxForState: function(state, box, workAreaBox, dashWidth, dashHeight, thumbnailsWidth, searchHeight, startY) { +const ControlsManagerLayoutVertical = { + _computeWorkspacesBoxForState(state, box, workAreaBox, dashWidth, dashHeight, thumbnailsWidth, searchHeight, startY) { const workspaceBox = box.copy(); let [width, height] = workspaceBox.get_size(); - const { x1: startX,/* y1: startY*/ } = workAreaBox; + // const { x1: startX/* y1: startY*/ } = workAreaBox; const { spacing } = this; - //const { expandFraction } = this._workspacesThumbnails; + // const { expandFraction } = this._workspacesThumbnails; const dash = Main.overview.dash; // including Dash to Dock and clones properties for compatibility - if (_dashIsDashToDock()) { + if (_Util.dashIsDashToDock()) { // Dash to Dock also always affects workAreaBox - Main.layoutManager._trackedActors.forEach((actor) => { + Main.layoutManager._trackedActors.forEach(actor => { if (actor.affectsStruts && actor.actor.width === dash.width) { if (dash._isHorizontal) { // disabled inteli-hide don't needs compensation // startY needs to be corrected in allocate() - if (dash.get_parent()?.get_parent()?.get_parent()?._intellihideIsEnabled) { + if (dash.get_parent()?.get_parent()?.get_parent()?._intellihideIsEnabled) height += dash.height; - } } else { width += dash.width; } @@ -647,9 +662,9 @@ var ControlsManagerLayoutVertical = { switch (state) { case ControlsState.HIDDEN: - // if PANEL_MODE == 2 (overview only) the affectStruts property stays on false to avoid stuttering + // if PANEL_OVERVIEW_ONLY, the affectStruts property is set to false to avoid stuttering // therefore we added panel height to startY for the overview allocation, - // but here we need to remove the correction since the panel will be in the hidden state + // but here we need to remove the correction because the panel will be in the hidden state if (opt.START_Y_OFFSET) { let [x, y] = workAreaBox.get_origin(); y -= opt.START_Y_OFFSET; @@ -674,27 +689,27 @@ var ControlsManagerLayoutVertical = { } workspaceBox.set_size(...workAreaBox.get_size()); } else { - // in PANEL_MODE 2 panel don't affects workArea height (affectStruts === false), it needs to be compensated + // if PANEL_OVERVIEW_ONLY, panel doesn't affect workArea height (affectStruts === false), it is necessary to compensate height = opt.PANEL_POSITION_TOP ? height : height - Main.panel.height; searchHeight = opt.SHOW_SEARCH_ENTRY ? searchHeight : 0; - wWidth = width - - (opt.DASH_VERTICAL ? dash.width : 0) - - (thumbnailsWidth) - - 4 * spacing - wHeight = height - - (opt.DASH_VERTICAL ? 0 : dashHeight) - - searchHeight - - 4 * spacing; + wWidth = width - + (opt.DASH_VERTICAL ? dash.width : 0) - + thumbnailsWidth - + 4 * spacing; + wHeight = height - + (opt.DASH_VERTICAL ? 0 : dashHeight) - + searchHeight - + 4 * spacing; const ratio = width / height; let wRatio = wWidth / wHeight; let scale = ratio / wRatio; if (scale > 1) { - wHeight = wHeight / scale; + wHeight /= scale; wWidth = wHeight * ratio; } else { - wWidth = wWidth * scale; + wWidth *= scale; wHeight = wWidth / ratio; } @@ -706,7 +721,7 @@ var ControlsManagerLayoutVertical = { let yOffset = 0; const yOffsetT = (opt.DASH_TOP ? dashHeight : 0) + searchHeight; - const yOffsetB = (opt.DASH_BOTTOM ? dashHeight : 0); + const yOffsetB = opt.DASH_BOTTOM ? dashHeight : 0; const yAvailableSpace = (height - yOffsetT - wHeight - yOffsetB) / 2; yOffset = yOffsetT + yAvailableSpace; @@ -723,7 +738,7 @@ var ControlsManagerLayoutVertical = { this._xAlignCenter = true; } - const wsBoxX = /*startX + */xOffset; + const wsBoxX = /* startX + */xOffset; wsBoxY = Math.round(startY + yOffset); workspaceBox.set_origin(Math.round(wsBoxX), Math.round(wsBoxY)); workspaceBox.set_size(Math.round(wWidth), Math.round(wHeight)); @@ -733,13 +748,11 @@ var ControlsManagerLayoutVertical = { return workspaceBox; }, - _getAppDisplayBoxForState: function(state, box, workAreaBox, searchHeight, dashWidth, dashHeight, thumbnailsWidth, startY) { + _getAppDisplayBoxForState(state, box, workAreaBox, searchHeight, dashWidth, dashHeight, thumbnailsWidth, startY) { const [width] = box.get_size(); const { x1: startX } = workAreaBox; - //const { y1: startY } = workAreaBox; + // const { y1: startY } = workAreaBox; let height = workAreaBox.get_height(); - // in PANEL_MODE 2 panel don't affects workArea height (affectStruts === false), it needs to be compensated - height = opt.PANEL_MODE === 2 ? height - Main.panel.height : height; const appDisplayBox = new Clutter.ActorBox(); const { spacing } = this; @@ -748,11 +761,11 @@ var ControlsManagerLayoutVertical = { const xOffsetL = (opt.WS_TMB_LEFT ? thumbnailsWidth : 0) + (opt.DASH_LEFT ? dashWidth : 0); const xOffsetR = (opt.WS_TMB_RIGHT ? thumbnailsWidth : 0) + (opt.DASH_RIGHT ? dashWidth : 0); const yOffsetT = (opt.DASH_TOP ? dashHeight : 0) + (opt.SHOW_SEARCH_ENTRY ? searchHeight : 0); - const yOffsetB = (opt.DASH_BOTTOM ? dashHeight : 0); - const adWidth = opt.CENTER_APP_GRID ? (width - 2 * Math.max (xOffsetL, xOffsetR) - 4 * spacing) : (width - xOffsetL - xOffsetR - 4 * spacing); + const yOffsetB = opt.DASH_BOTTOM ? dashHeight : 0; + const adWidth = opt.CENTER_APP_GRID ? width - 2 * Math.max(xOffsetL, xOffsetR) - 4 * spacing : width - xOffsetL - xOffsetR - 4 * spacing; const adHeight = height - yOffsetT - yOffsetB - 4 * spacing; - const appDisplayX = opt.CENTER_APP_GRID ? ((width - adWidth) / 2) : (xOffsetL + 2 * spacing); + const appDisplayX = opt.CENTER_APP_GRID ? (width - adWidth) / 2 : xOffsetL + 2 * spacing; const appDisplayY = startY + yOffsetT + 2 * spacing; switch (state) { @@ -786,7 +799,7 @@ var ControlsManagerLayoutVertical = { return appDisplayBox; }, - vfunc_allocate: function(container, box) { + vfunc_allocate(container, box) { const childBox = new Clutter.ActorBox(); const { spacing } = this; @@ -794,7 +807,7 @@ var ControlsManagerLayoutVertical = { const monitor = Main.layoutManager.findMonitorForActor(this._container); const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index); const startX = workArea.x - monitor.x; - // if PANEL_MODE == 2 (overview only) the affectStruts property stays on false to avoid stuttering + // if PANEL_OVERVIEW_ONLY, the affectStruts property is set to false to avoid stuttering // therefore we need to add panel height to startY let startY = workArea.y - monitor.y + opt.START_Y_OFFSET; @@ -817,7 +830,7 @@ var ControlsManagerLayoutVertical = { // dash cloud be overridden by the Dash to Dock clone const dash = Main.overview.dash; - if (_dashIsDashToDock()) { + if (_Util.dashIsDashToDock()) { // if Dash to Dock replaced the default dash and its inteli-hide id disabled we need to compensate for affected startY if (!Main.overview.dash.get_parent()?.get_parent()?.get_parent()?._intellihideIsEnabled) { if (Main.panel.y === monitor.y) @@ -835,9 +848,8 @@ var ControlsManagerLayoutVertical = { [, dashHeight] = this._dash.get_preferred_height(dashWidth); dashWidth = Math.min(dashWidth, maxDashWidth); dashHeight = Math.min(dashHeight, height); - } else if (!opt.WS_TMB_FULL) { - this._dash.setMaxSize(width, maxDashHeight); + this._dash.setMaxSize(width, maxDashHeight); [, dashHeight] = this._dash.get_preferred_height(width); [, dashWidth] = this._dash.get_preferred_width(dashHeight); dashHeight = Math.min(dashHeight, maxDashHeight); @@ -845,46 +857,48 @@ var ControlsManagerLayoutVertical = { } } + const transitionParams = this._stateAdjustment.getStateTransitionParams(); + // Workspace Thumbnails let wsTmbWidth = 0; let wsTmbHeight = 0; if (this._workspacesThumbnails.visible) { - //const REDUCE_WS_TMB_IF_NEEDED = (this._searchController._searchActive && opt.CENTER_SEARCH_VIEW) || opt.CENTER_APP_GRID; - - const { expandFraction } = this._workspacesThumbnails; - const dashHeightReservation = (!opt.WS_TMB_FULL && !opt.DASH_VERTICAL) ? dashHeight : 0; - wsTmbHeight = opt.WS_TMB_FULL - ? height - spacing - : height - (opt.DASH_VERTICAL ? 0 : dashHeightReservation) - spacing; - - wsTmbWidth = this._workspacesThumbnails.get_preferred_custom_width(wsTmbHeight)[0]; - wsTmbWidth = Math.round(Math.min( - wsTmbWidth * expandFraction, - width * opt.MAX_THUMBNAIL_SCALE - )); + // const { expandFraction } = this._workspacesThumbnails; + const dashHeightReservation = !opt.WS_TMB_FULL && !opt.DASH_VERTICAL ? dashHeight : 0; + + let maxScale = opt.MAX_THUMBNAIL_SCALE; + if (!opt.MAX_THUMBNAIL_SCALE_STABLE) { + const initState = transitionParams.initialState === ControlsState.APP_GRID ? opt.MAX_THUMBNAIL_SCALE_APPGRID : opt.MAX_THUMBNAIL_SCALE; + const finalState = transitionParams.finalState === ControlsState.APP_GRID ? opt.MAX_THUMBNAIL_SCALE_APPGRID : opt.MAX_THUMBNAIL_SCALE; + maxScale = Util.lerp(initState, finalState, transitionParams.progress); + } - /*if (opt.REDUCE_WS_TMB_IF_NEEDED) { - const searchAllocation = this._searchController._searchResults._content.allocation; - const searchWidth = searchAllocation.x2 - searchAllocation.x1; - wsTmbWidth = Math.clamp((width - searchWidth) / 2 - spacing, width * 0.05, wsTmbWidth); - }*/ + wsTmbWidth = width * maxScale; + let totalTmbSpacing; + [totalTmbSpacing, wsTmbHeight] = this._workspacesThumbnails.get_preferred_custom_height(wsTmbWidth); + wsTmbHeight += totalTmbSpacing; - wsTmbHeight = Math.round(Math.min(this._workspacesThumbnails.get_preferred_custom_height(wsTmbWidth)[1], wsTmbHeight)); + const wsTmbHeightMax = height - dashHeightReservation; + + if (wsTmbHeight > wsTmbHeightMax) { + wsTmbHeight = wsTmbHeightMax; + wsTmbWidth = this._workspacesThumbnails.get_preferred_custom_width(wsTmbHeight)[1]; + } let wsTmbX; - if (opt.WS_TMB_RIGHT) { + if (opt.WS_TMB_RIGHT) wsTmbX = Math.round(startX + width - (opt.DASH_RIGHT ? dashWidth : 0) - wsTmbWidth - spacing / 2); - } else { + else wsTmbX = Math.round((opt.DASH_LEFT ? dashWidth : 0) + spacing / 2); - } + let wstOffset = (height - wsTmbHeight - (opt.DASH_VERTICAL ? 0 : dashHeightReservation)) / 2; - wstOffset = wstOffset - opt.WS_TMB_POSITION_ADJUSTMENT * (wstOffset - spacing / 2); - let wsTmbY = Math.round(startY + ((dashHeightReservation && opt.DASH_TOP) ? dashHeight : 0) + wstOffset); + wstOffset -= opt.WS_TMB_POSITION_ADJUSTMENT * (wstOffset - spacing / 2); + let wsTmbY = Math.round(startY + (dashHeightReservation && opt.DASH_TOP ? dashHeight : 0) + wstOffset); childBox.set_origin(wsTmbX, wsTmbY); - childBox.set_size(wsTmbWidth, wsTmbHeight); + childBox.set_size(Math.round(wsTmbWidth), Math.round(wsTmbHeight)); this._workspacesThumbnails.allocate(childBox); } @@ -903,23 +917,21 @@ var ControlsManagerLayoutVertical = { let dashX, dashY, offset; if (opt.DASH_RIGHT) dashX = width - dashWidth; - else if (opt.DASH_LEFT) { + else if (opt.DASH_LEFT) dashX = 0; - } + else if (opt.DASH_TOP) dashY = startY; else dashY = startY + height - dashHeight; if (!opt.DASH_VERTICAL) { - offset = (width - (((opt.WS_TMB_FULL || opt.CENTER_DASH_WS) && !this._xAlignCenter) ? wsTmbWidth : 0) - dashWidth) / 2; - offset = offset - opt.DASH_POSITION_ADJUSTMENT * (offset - spacing / 2); + offset = (width - ((opt.WS_TMB_FULL || opt.CENTER_DASH_WS) && !this._xAlignCenter ? wsTmbWidth : 0) - dashWidth) / 2; + offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - spacing / 2); dashX = offset; if ((opt.WS_TMB_FULL || opt.CENTER_DASH_WS) && !this._xAlignCenter) { - if (opt.WS_TMB_RIGHT) { - //dashX = Math.min(dashX, width - dashWidth - (wsTmbWidth ? wsTmbWidth : 0)); - } else { + if (!opt.WS_TMB_RIGHT) { dashX = (wsTmbWidth ? wsTmbWidth : 0) + offset; dashX = Math.max(dashX, wsTmbWidth ? wsTmbWidth + spacing : 0); dashX = Math.min(dashX, width - dashWidth - spacing); @@ -927,11 +939,11 @@ var ControlsManagerLayoutVertical = { } if (opt.WS_TMB_FULL && !opt.CENTER_DASH_WS) { dashX = opt.WS_TMB_RIGHT - ? Math.min(width - wsTmbWidth - dashWidth, dashX + (wsTmbWidth) / 2 * (1 - Math.abs(opt.DASH_POSITION_ADJUSTMENT))) - : Math.max(wsTmbWidth, dashX - (wsTmbWidth) / 2 * (1 - Math.abs(opt.DASH_POSITION_ADJUSTMENT))); + ? Math.min(width - wsTmbWidth - dashWidth, dashX + wsTmbWidth / 2 * (1 - Math.abs(opt.DASH_POSITION_ADJUSTMENT))) + : Math.max(wsTmbWidth, dashX - wsTmbWidth / 2 * (1 - Math.abs(opt.DASH_POSITION_ADJUSTMENT))); } } else { - const offset = (height - dashHeight) / 2; + offset = (height - dashHeight) / 2; dashY = startY + (offset - opt.DASH_POSITION_ADJUSTMENT * offset); } @@ -940,13 +952,12 @@ var ControlsManagerLayoutVertical = { this._dash.allocate(childBox); } - availableHeight -= (opt.DASH_VERTICAL ? 0 : dashHeight + spacing); + availableHeight -= opt.DASH_VERTICAL ? 0 : dashHeight + spacing; let [searchHeight] = this._searchEntry.get_preferred_height(width - wsTmbWidth); // Workspaces let params = [box, workAreaBox, dashWidth, dashHeight, wsTmbWidth, searchHeight, startY]; - const transitionParams = this._stateAdjustment.getStateTransitionParams(); // Update cached boxes for (const state of Object.values(ControlsState)) { @@ -955,9 +966,9 @@ var ControlsManagerLayoutVertical = { } let workspacesBox; - if (!transitionParams.transitioning) { + if (!transitionParams.transitioning) workspacesBox = this._cachedWorkspaceBoxes.get(transitionParams.currentState); - } + if (!workspacesBox) { const initialBox = this._cachedWorkspaceBoxes.get(transitionParams.initialState); const finalBox = this._cachedWorkspaceBoxes.get(transitionParams.finalState); @@ -968,17 +979,14 @@ var ControlsManagerLayoutVertical = { // Search entry const searchXoffset = (opt.DASH_LEFT ? dashWidth : 0) + spacing + (opt.WS_TMB_RIGHT ? 0 : wsTmbWidth + spacing); - //let [searchHeight] = this._searchEntry.get_preferred_height(width - wsTmbWidth); // Y position under top Dash let searchEntryX, searchEntryY; - /*if (opt.OVERVIEW_MODE2 && !opt.DASH_TOP && !opt.WS_TMB_TOP) { - searchEntryY = 7; - } else*/ if (opt.DASH_TOP) { + if (opt.DASH_TOP) searchEntryY = startY + dashHeight - spacing; - } else { + else searchEntryY = startY; - } + searchEntryX = searchXoffset; let searchWidth = width - 2 * spacing - wsTmbWidth - (opt.DASH_VERTICAL ? dashWidth : 0); // xAlignCenter is given by wsBox @@ -996,24 +1004,21 @@ var ControlsManagerLayoutVertical = { availableHeight -= searchHeight + spacing; - // AppDisplay - state, box, workAreaBox, searchHeight, dashHeight, appGridBox, wsTmbWidth - //if (this._appDisplay.visible) { - - params = [box, workAreaBox, searchHeight, dashWidth, dashHeight, wsTmbWidth, startY]; // send startY, can be compensated - let appDisplayBox; - if (!transitionParams.transitioning) { - appDisplayBox = + // if (this._appDisplay.visible)... ? Can cause problems + params = [box, workAreaBox, searchHeight, dashWidth, dashHeight, wsTmbWidth, startY]; // send startY, can be corrected + let appDisplayBox; + if (!transitionParams.transitioning) { + appDisplayBox = this._getAppDisplayBoxForState(transitionParams.currentState, ...params); - } else { - const initialBox = + } else { + const initialBox = this._getAppDisplayBoxForState(transitionParams.initialState, ...params); - const finalBox = + const finalBox = this._getAppDisplayBoxForState(transitionParams.finalState, ...params); - appDisplayBox = initialBox.interpolate(finalBox, transitionParams.progress); - } - this._appDisplay.allocate(appDisplayBox); - //} + appDisplayBox = initialBox.interpolate(finalBox, transitionParams.progress); + } + this._appDisplay.allocate(appDisplayBox); // Search if (opt.CENTER_SEARCH_VIEW) { @@ -1028,31 +1033,30 @@ var ControlsManagerLayoutVertical = { this._searchController.allocate(childBox); this._runPostAllocation(); - } -} + }, +}; -var ControlsManagerLayoutHorizontal = { - _computeWorkspacesBoxForState: function(state, box, workAreaBox, dashWidth, dashHeight, thumbnailsHeight, searchHeight, startY) { +const ControlsManagerLayoutHorizontal = { + _computeWorkspacesBoxForState(state, box, workAreaBox, dashWidth, dashHeight, thumbnailsHeight, searchHeight, startY) { const workspaceBox = box.copy(); let [width, height] = workspaceBox.get_size(); - let { x1: startX/*, y1: startY*/ } = workAreaBox; + // let { x1: startX/* , y1: startY*/ } = workAreaBox; const { spacing } = this; - //const { expandFraction } = this._workspacesThumbnails; + // const { expandFraction } = this._workspacesThumbnails; const dash = Main.overview.dash; // including Dash to Dock and clones properties for compatibility - if (_dashIsDashToDock()) { + if (_Util.dashIsDashToDock()) { // Dash to Dock always affects workAreaBox - Main.layoutManager._trackedActors.forEach((actor) => { + Main.layoutManager._trackedActors.forEach(actor => { if (actor.affectsStruts && actor.actor.width === dash.width) { if (dash._isHorizontal) { // disabled inteli-hide don't need compensation // startY needs to be corrected in allocate() - if (dash.get_parent()?.get_parent()?.get_parent()?._intellihideIsEnabled) { + if (dash.get_parent()?.get_parent()?.get_parent()?._intellihideIsEnabled) height += dash.height; - } else if (opt.DASH_TOP) { + else if (opt.DASH_TOP) height += dash.height; - } } else { width += dash.width; } @@ -1064,7 +1068,7 @@ var ControlsManagerLayoutHorizontal = { switch (state) { case ControlsState.HIDDEN: - // if PANEL_MODE == 2 (overview only) the affectStruts property stays on false to avoid stuttering + // if PANEL_OVERVIEW_ONLY, the affectStruts property is set to false to avoid stuttering // therefore we added panel height to startY for the overview allocation, // but here we need to remove the correction since the panel will be in the hidden state if (opt.START_Y_OFFSET) { @@ -1091,28 +1095,28 @@ var ControlsManagerLayoutHorizontal = { } workspaceBox.set_size(...workAreaBox.get_size()); } else { - // in PANEL_MODE 2 panel don't affects workArea height (affectStruts === false), it needs to be compensated + // if PANEL_OVERVIEW_ONLY, panel doesn't affect workArea height (affectStruts === false), it is necessary to compensate height = opt.PANEL_POSITION_TOP ? height : height - Main.panel.height; searchHeight = opt.SHOW_SEARCH_ENTRY ? searchHeight : 0; - wWidth = width - - spacing - - (opt.DASH_VERTICAL ? dashWidth : 0) - - 4 * spacing; - wHeight = height - - (opt.DASH_VERTICAL ? spacing : (dashHeight ? dashHeight : 0)) - - (thumbnailsHeight ? thumbnailsHeight : 0) - - searchHeight - - 4 * spacing; + wWidth = width - + spacing - + (opt.DASH_VERTICAL ? dashWidth : 0) - + 4 * spacing; + wHeight = height - + (opt.DASH_VERTICAL ? spacing : dashHeight) - + thumbnailsHeight - + searchHeight - + 4 * spacing; const ratio = width / height; let wRatio = wWidth / wHeight; let scale = ratio / wRatio; if (scale > 1) { - wHeight = wHeight / scale; + wHeight /= scale; wWidth = wHeight * ratio; } else { - wWidth = wWidth * scale; + wWidth *= scale; wHeight = wWidth / ratio; } @@ -1141,8 +1145,8 @@ var ControlsManagerLayoutHorizontal = { this._xAlignCenter = true; } - wsBoxX = /*startX + */xOffset; - wsBoxY = Math.round(startY + yOffset) + wsBoxX = /* startX + */xOffset; + wsBoxY = Math.round(startY + yOffset); workspaceBox.set_origin(Math.round(wsBoxX), Math.round(wsBoxY)); workspaceBox.set_size(Math.round(wWidth), Math.round(wHeight)); } @@ -1151,25 +1155,24 @@ var ControlsManagerLayoutHorizontal = { return workspaceBox; }, - _getAppDisplayBoxForState: function(state, box, workAreaBox, searchHeight, dashWidth, dashHeight, thumbnailsHeight, startY) { + _getAppDisplayBoxForState(state, box, workAreaBox, searchHeight, dashWidth, dashHeight, thumbnailsHeight, startY) { const [width] = box.get_size(); const { x1: startX } = workAreaBox; - //const { y1: startY } = workAreaBox; + // const { y1: startY } = workAreaBox; let height = workAreaBox.get_height(); - // in PANEL_MODE 2 panel don't affects workArea height (affectStruts === false), it needs to be compensated - height = opt.PANEL_MODE === 2 ? height - Main.panel.height : height; const appDisplayBox = new Clutter.ActorBox(); const { spacing } = this; - const yOffsetT = (opt.WS_TMB_TOP ? thumbnailsHeight : 0) + (opt.DASH_TOP ? dashHeight : 0) + (opt.SHOW_SEARCH_ENTRY ? searchHeight : 0); + const yOffsetT = (opt.WS_TMB_TOP ? thumbnailsHeight : 0) + (opt.DASH_TOP ? dashHeight : 0) + (opt.SHOW_SEARCH_ENTRY ? searchHeight : 0) + 2 * spacing; const yOffsetB = (opt.WS_TMB_BOTTOM ? thumbnailsHeight : 0) + (opt.DASH_BOTTOM ? dashHeight : 0); - const xOffsetL = (opt.DASH_LEFT ? dashWidth : 0); - const xOffsetR = (opt.DASH_RIGHT ? dashWidth : 0); - const adWidth = opt.CENTER_APP_GRID ? (width - 2 * Math.max (xOffsetL, xOffsetR) - 4 * spacing) : (width - xOffsetL - xOffsetR - 4 * spacing); + const xOffsetL = opt.DASH_LEFT ? dashWidth : 0; + const xOffsetR = opt.DASH_RIGHT ? dashWidth : 0; + const hSpacing = xOffsetL + xOffsetR ? 2 * spacing : 0; + const adWidth = opt.CENTER_APP_GRID ? width - 2 * Math.max(xOffsetL, xOffsetR) - 2 * hSpacing : width - xOffsetL - xOffsetR - 2 * hSpacing; const adHeight = height - yOffsetT - yOffsetB - 4 * spacing; - const appDisplayX = opt.CENTER_APP_GRID ? ((width - adWidth) / 2) : (xOffsetL + 2 * spacing); - const appDisplayY = startY + yOffsetT + 2 * spacing; + const appDisplayX = opt.CENTER_APP_GRID ? (width - adWidth) / 2 : xOffsetL + hSpacing; + const appDisplayY = startY + yOffsetT + hSpacing; switch (state) { case ControlsState.HIDDEN: @@ -1202,7 +1205,7 @@ var ControlsManagerLayoutHorizontal = { return appDisplayBox; }, - vfunc_allocate: function(container, box) { + vfunc_allocate(container, box) { const childBox = new Clutter.ActorBox(); const { spacing } = this; @@ -1210,7 +1213,7 @@ var ControlsManagerLayoutHorizontal = { const monitor = Main.layoutManager.findMonitorForActor(this._container); const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index); const startX = workArea.x - monitor.x; - // if PANEL_MODE == 2 (overview only) the affectStruts property stays on false to avoid stuttering + // if PANEL_OVERVIEW_ONLY, the affectStruts property is set to false to avoid stuttering // therefore we need to add panel height to startY let startY = workArea.y - monitor.y + opt.START_Y_OFFSET; const workAreaBox = new Clutter.ActorBox(); @@ -1220,7 +1223,7 @@ var ControlsManagerLayoutHorizontal = { box.x1 += startX; let [width, height] = box.get_size(); // if panel is at bottom position, - // compensate the height of the available box (the box size is calculated for top panel) + // compensate for the height of the available box (the box size is calculated for top panel) height = opt.PANEL_POSITION_TOP ? height : height - Main.panel.height; let availableHeight = height; @@ -1232,11 +1235,11 @@ var ControlsManagerLayoutHorizontal = { // dash cloud be overridden by the Dash to Dock clone const dash = Main.overview.dash; - if (_dashIsDashToDock()) { + if (_Util.dashIsDashToDock()) { // if Dash to Dock replaced the default dash and its inteli-hide is disabled we need to compensate for affected startY if (!Main.overview.dash.get_parent()?.get_parent()?.get_parent()?._intellihideIsEnabled) { - if (Main.panel.y === monitor.y) - startY = Main.panel.height + spacing; + // if (Main.panel.y === monitor.y) + // startY = Main.panel.height + spacing; } dashHeight = dash.height; dashWidth = dash.width; @@ -1251,7 +1254,6 @@ var ControlsManagerLayoutHorizontal = { [, dashWidth] = this._dash.get_preferred_width(dashHeight); dashHeight = Math.min(dashHeight, maxDashHeight); dashWidth = Math.min(dashWidth, width - spacing); - } else if (!opt.WS_TMB_FULL) { this._dash.setMaxSize(maxDashWidth, height); [, dashWidth] = this._dash.get_preferred_width(height); @@ -1263,44 +1265,53 @@ var ControlsManagerLayoutHorizontal = { let [searchHeight] = this._searchEntry.get_preferred_height(width); + const transitionParams = this._stateAdjustment.getStateTransitionParams(); + + // Workspace Thumbnails let wsTmbWidth = 0; let wsTmbHeight = 0; if (this._workspacesThumbnails.visible) { - const { expandFraction } = this._workspacesThumbnails; - const dashWidthReservation = (!opt.WS_TMB_FULL && opt.DASH_VERTICAL) ? dashWidth : 0; + // const { expandFraction } = this._workspacesThumbnails; + const dashWidthReservation = !opt.WS_TMB_FULL && opt.DASH_VERTICAL ? dashWidth : 0; + + let maxScale = opt.MAX_THUMBNAIL_SCALE; + if (!opt.MAX_THUMBNAIL_SCALE_STABLE) { + const initState = transitionParams.initialState === ControlsState.APP_GRID ? opt.MAX_THUMBNAIL_SCALE_APPGRID : opt.MAX_THUMBNAIL_SCALE; + const finalState = transitionParams.finalState === ControlsState.APP_GRID ? opt.MAX_THUMBNAIL_SCALE_APPGRID : opt.MAX_THUMBNAIL_SCALE; + maxScale = Util.lerp(initState, finalState, transitionParams.progress); + } - wsTmbWidth = opt.WS_TMB_FULL - ? width - : width - (opt.DASH_VERTICAL ? 0 : dashWidthReservation); + wsTmbHeight = height * maxScale; + let totalTmbSpacing; + [totalTmbSpacing, wsTmbWidth] = this._workspacesThumbnails.get_preferred_custom_width(wsTmbHeight); + wsTmbWidth += totalTmbSpacing; - wsTmbHeight = this._workspacesThumbnails.get_preferred_height(wsTmbWidth)[0]; - wsTmbHeight = Math.round(Math.min( - wsTmbHeight * expandFraction, - height * opt.MAX_THUMBNAIL_SCALE - )); + const wsTmbWidthMax = opt.WS_TMB_FULL + ? width + : width - (opt.DASH_VERTICAL ? 0 : dashWidthReservation); - wsTmbWidth = Math.round(Math.min(this._workspacesThumbnails.get_preferred_custom_width(wsTmbHeight)[1], wsTmbWidth)); + if (wsTmbWidth > wsTmbWidthMax) { + wsTmbWidth = wsTmbWidthMax; + wsTmbHeight = this._workspacesThumbnails.get_preferred_custom_height(wsTmbWidth)[1]; + } let wsTmbY; - if (opt.WS_TMB_TOP) { - wsTmbY = Math.round(startY + /*searchHeight + */(opt.DASH_TOP ? dashHeight : spacing / 2)); - } else { - //const boxY = workArea.y - monitor.y; // startY might be compensated - //wsTmbY = Math.round(boxY + height - (DASH_BOTTOM ? dashHeight : 0) - wsTmbHeight); + if (opt.WS_TMB_TOP) + wsTmbY = Math.round(startY + /* searchHeight + */(opt.DASH_TOP ? dashHeight : spacing / 2)); + else wsTmbY = Math.round(startY + height - (opt.DASH_BOTTOM ? dashHeight : 0) - wsTmbHeight); - } let wstOffset = (width - wsTmbWidth) / 2; - wstOffset = wstOffset - opt.WS_TMB_POSITION_ADJUSTMENT * wstOffset; + wstOffset -= opt.WS_TMB_POSITION_ADJUSTMENT * (wstOffset - spacing / 2); let wsTmbX = Math.round(Math.clamp( startX + wstOffset, - startX + (opt.DASH_LEFT ? dashWidthReservation + spacing / 2 : spacing / 2), - width - wsTmbWidth - startX - (opt.DASH_RIGHT ? dashWidthReservation + spacing / 2 : spacing / 2) + startX + (opt.DASH_LEFT ? dashWidthReservation : 0), + width - wsTmbWidth - startX - (opt.DASH_RIGHT ? dashWidthReservation : 0) )); childBox.set_origin(wsTmbX, wsTmbY); - childBox.set_size(wsTmbWidth, wsTmbHeight); + childBox.set_size(Math.round(wsTmbWidth), Math.round(wsTmbHeight)); this._workspacesThumbnails.allocate(childBox); @@ -1309,7 +1320,6 @@ var ControlsManagerLayoutHorizontal = { if (this._dash.visible) { - //const wMaxHeight = height - spacing - wsTmbHeight - 2 * spacing - (DASH_VERTICAL ? 0 : dashHeight + spacing); if (opt.WS_TMB_FULL && opt.DASH_VERTICAL) { const wMaxHeight = height - spacing - wsTmbHeight; this._dash.setMaxSize(maxDashWidth, wMaxHeight); @@ -1320,31 +1330,29 @@ var ControlsManagerLayoutHorizontal = { } let dashX, dashY, offset; - if (opt.DASH_RIGHT) { + if (opt.DASH_RIGHT) dashX = width - dashWidth; - } else if (opt.DASH_LEFT) { + else if (opt.DASH_LEFT) dashX = 0; - } else if (opt.DASH_TOP) { + else if (opt.DASH_TOP) dashY = startY; - } else { + else dashY = startY + height - dashHeight; - } + if (opt.DASH_VERTICAL) { if (opt.WS_TMB_FULL) { offset = (height - dashHeight - wsTmbHeight) / 2; if (opt.WS_TMB_TOP) { - offset = offset - opt.DASH_POSITION_ADJUSTMENT * (offset - spacing / 2); + offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - spacing / 2); dashY = startY + offset + wsTmbHeight; - //dashY = Math.max(dashY, startY + wsTmbHeight); } else { - offset = offset - opt.DASH_POSITION_ADJUSTMENT * (offset - spacing / 2); + offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - spacing / 2); dashY = startY + offset; - //dashY = Math.max(dashY, height - wsTmbHeight - dashHeight - 3 * spacing); } } else { offset = (height - dashHeight) / 2; - offset = offset - opt.DASH_POSITION_ADJUSTMENT * (offset - spacing / 2); + offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - spacing / 2); dashY = startY + offset; } } else { @@ -1357,13 +1365,10 @@ var ControlsManagerLayoutHorizontal = { this._dash.allocate(childBox); } - availableHeight -= (opt.DASH_VERTICAL ? 0 : dashHeight); - - /*let [searchHeight] = this._searchEntry.get_preferred_height(width);*/ + availableHeight -= opt.DASH_VERTICAL ? 0 : dashHeight; // Workspaces let params = [box, workAreaBox, dashWidth, dashHeight, wsTmbHeight, searchHeight, startY]; - const transitionParams = this._stateAdjustment.getStateTransitionParams(); // Update cached boxes for (const state of Object.values(ControlsState)) { @@ -1372,9 +1377,9 @@ var ControlsManagerLayoutHorizontal = { } let workspacesBox; - if (!transitionParams.transitioning) { + if (!transitionParams.transitioning) workspacesBox = this._cachedWorkspaceBoxes.get(transitionParams.currentState); - } + if (!workspacesBox) { const initialBox = this._cachedWorkspaceBoxes.get(transitionParams.initialState); const finalBox = this._cachedWorkspaceBoxes.get(transitionParams.finalState); @@ -1385,17 +1390,14 @@ var ControlsManagerLayoutHorizontal = { // Search entry const searchXoffset = (opt.DASH_LEFT ? dashWidth : 0) + spacing; - //let [searchHeight] = this._searchEntry.get_preferred_height(width - wsTmbWidth); // Y position under top Dash let searchEntryX, searchEntryY; - /*if (opt.OVERVIEW_MODE2 && !opt.DASH_TOP && !opt.WS_TMB_TOP) { - searchEntryY = 7; - } else */if (opt.DASH_TOP) { + if (opt.DASH_TOP) searchEntryY = startY + (opt.WS_TMB_TOP ? wsTmbHeight : 0) + dashHeight - spacing; - } else { + else searchEntryY = startY + (opt.WS_TMB_TOP ? wsTmbHeight + spacing : 0); - } + searchEntryX = searchXoffset; let searchWidth = width - 2 * spacing - (opt.DASH_VERTICAL ? dashWidth : 0); // xAlignCenter is given by wsBox @@ -1413,23 +1415,21 @@ var ControlsManagerLayoutHorizontal = { availableHeight -= searchHeight + spacing; - // AppDisplay - state, box, workAreaBox, searchHeight, dashHeight, appGridBox, wsTmbWidth - //if (this._appDisplay.visible) { - params = [box, workAreaBox, searchHeight, dashWidth, dashHeight, wsTmbHeight, startY]; - let appDisplayBox; - if (!transitionParams.transitioning) { - appDisplayBox = + // if (this._appDisplay.visible)... ? Can cause problems + params = [box, workAreaBox, searchHeight, dashWidth, dashHeight, wsTmbHeight, startY]; + let appDisplayBox; + if (!transitionParams.transitioning) { + appDisplayBox = this._getAppDisplayBoxForState(transitionParams.currentState, ...params); - } else { - const initialBox = + } else { + const initialBox = this._getAppDisplayBoxForState(transitionParams.initialState, ...params); - const finalBox = + const finalBox = this._getAppDisplayBoxForState(transitionParams.finalState, ...params); - appDisplayBox = initialBox.interpolate(finalBox, transitionParams.progress); - } - this._appDisplay.allocate(appDisplayBox); - //} + appDisplayBox = initialBox.interpolate(finalBox, transitionParams.progress); + } + this._appDisplay.allocate(appDisplayBox); // Search if (opt.CENTER_SEARCH_VIEW) { @@ -1444,8 +1444,8 @@ var ControlsManagerLayoutHorizontal = { this._searchController.allocate(childBox); this._runPostAllocation(); - } -} + }, +}; // same copy of this function should be available in OverviewControls and WorkspacesView function _getFitModeForState(state) { diff --git a/extensions/vertical-workspaces/panel.js b/extensions/vertical-workspaces/lib/panel.js index 2f7143d..3f44ae7 100644 --- a/extensions/vertical-workspaces/panel.js +++ b/extensions/vertical-workspaces/lib/panel.js @@ -1,7 +1,7 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * panel.js - * + * * @author GdH <G-dH@github.com> * @copyright 2022 - 2023 * @license GPL-3.0 @@ -10,92 +10,139 @@ 'use strict'; +const { GLib } = imports.gi; const Main = imports.ui.main; const Me = imports.misc.extensionUtils.getCurrentExtension(); +const _Util = Me.imports.lib.util; const ANIMATION_TIME = imports.ui.overview.ANIMATION_TIME; let opt; +let _firstRun = true; + let _showingOverviewConId; let _hidingOverviewConId; let _styleChangedConId; function update(reset = false) { - opt = Me.imports.settings.opt; - const panelBox = Main.layoutManager.panelBox; - const panelHeight = Main.panel.height; // panelBox height can be 0 after shell start + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('panelModule', true); + // Avoid conflict with other extensions + const conflict = _Util.getEnabledExtensions('dash-to-panel').length || + _Util.getEnabledExtensions('hidetopbar').length; + reset = reset || (!_firstRun && !moduleEnabled); - const geometry = global.display.get_monitor_geometry(global.display.get_primary_monitor()); - if (reset || opt.PANEL_POSITION_TOP) { - panelBox.set_position(geometry.x, geometry.y); - } else { - panelBox.set_position(geometry.x, geometry.y + geometry.height - panelHeight); - } + // don't even touch this module if disabled or in potential conflict + if (_firstRun && (reset || conflict)) + return; - if (!_styleChangedConId) { - Main.panel.connect('style-changed', ()=> Main.panel.remove_style_pseudo_class('overview')); - } + _firstRun = false; - if (reset || opt.PANEL_MODE === 0) { - //_disconnectPanel(); - _disconnectOverview() + const panelBox = Main.layoutManager.panelBox; + if (reset || !moduleEnabled) { + // _disconnectPanel(); + reset = true; + _setPanelPosition(reset); + _updateOverviewConnection(reset); _reparentPanel(false); - _showPanel(); - if (_styleChangedConId) { - Main.panel.disconnect(_styleChangedConId); - _styleChangedConId = 0; - } + _updateStyleChangedConnection(reset); panelBox.translation_y = 0; - panelBox.opacity = 255; + Main.panel.opacity = 255; + _setPanelStructs(true); + return; + } + + _setPanelPosition(); + _updateStyleChangedConnection(); + + if (opt.PANEL_MODE === 0) { + _updateOverviewConnection(true); + _reparentPanel(false); + panelBox.translation_y = 0; + Main.panel.opacity = 255; + _setPanelStructs(true); } else if (opt.PANEL_MODE === 1) { if (opt.SHOW_WS_PREVIEW_BG) { _reparentPanel(true); if (opt.OVERVIEW_MODE2) { // in OM2 if the panel has been moved to the overviewGroup move panel above all Main.layoutManager.overviewGroup.set_child_above_sibling(panelBox, null); + _updateOverviewConnection(); } else { // otherwise move the panel below overviewGroup so it can get below workspacesDisplay Main.layoutManager.overviewGroup.set_child_below_sibling(panelBox, Main.overview._overview); + _updateOverviewConnection(true); } _showPanel(true); } else { // if ws preview bg is disabled, panel can stay in uiGroup _reparentPanel(false); _showPanel(false); - if (!_hidingOverviewConId) - _hidingOverviewConId = Main.overview.connect('hiding', () => { - if ((!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2)) { - _showPanel(false); - } - }); - if (!_showingOverviewConId) - _showingOverviewConId = Main.overview.connect('showing', () => { - if ((!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2)) { - _showPanel(true); - } - }); + _updateOverviewConnection(); } - - _connectPanel(); + // _connectPanel(); } else if (opt.PANEL_MODE === 2) { - _disconnectOverview(); + _updateOverviewConnection(true); _reparentPanel(false); _showPanel(false); - _connectPanel(); + // _connectPanel(); } - _setPanelStructs(reset || opt.PANEL_MODE === 0); + _setPanelStructs(opt.PANEL_MODE === 0); + Main.layoutManager._updateHotCorners(); +} + +function _setPanelPosition(reset = false) { + const geometry = global.display.get_monitor_geometry(global.display.get_primary_monitor()); + const panelBox = Main.layoutManager.panelBox; + const panelHeight = Main.panel.height; // panelBox height can be 0 after shell start + + if (opt.PANEL_POSITION_TOP || reset) + panelBox.set_position(geometry.x, geometry.y); + else + panelBox.set_position(geometry.x, geometry.y + geometry.height - panelHeight); } -function _disconnectOverview() { - if (_hidingOverviewConId) { - Main.overview.disconnect(_hidingOverviewConId); - _hidingOverviewConId = 0; +function _updateStyleChangedConnection(reset = false) { + if (reset) { + if (_styleChangedConId) { + Main.panel.disconnect(_styleChangedConId); + _styleChangedConId = 0; + } + } else if (!_styleChangedConId) { + Main.panel.connect('style-changed', () => { + if (opt.PANEL_MODE === 1) + Main.panel.add_style_pseudo_class('overview'); + else if (opt.OVERVIEW_MODE2) + Main.panel.remove_style_pseudo_class('overview'); + }); } - if (_showingOverviewConId) { - Main.overview.disconnect(_showingOverviewConId); - _showingOverviewConId = 0; +} + +function _updateOverviewConnection(reset = false) { + if (reset) { + if (_hidingOverviewConId) { + Main.overview.disconnect(_hidingOverviewConId); + _hidingOverviewConId = 0; + } + if (_showingOverviewConId) { + Main.overview.disconnect(_showingOverviewConId); + _showingOverviewConId = 0; + } + } else { + if (!_hidingOverviewConId) { + _hidingOverviewConId = Main.overview.connect('hiding', () => { + if (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2) + _showPanel(false); + }); + } + if (!_showingOverviewConId) { + _showingOverviewConId = Main.overview.connect('showing', () => { + if (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2 || Main.layoutManager.panelBox.translation_y) + _showPanel(true); + }); + } } } @@ -106,7 +153,7 @@ function _reparentPanel(reparent = false) { Main.layoutManager.overviewGroup.add_child(panel); } else if (!reparent && panel.get_parent() === Main.layoutManager.overviewGroup) { Main.layoutManager.overviewGroup.remove_child(panel); - // return the panel at default position, pane shouldn't cover objects that should be above + // return the panel at default position, panel shouldn't cover objects that should be above Main.layoutManager.uiGroup.insert_child_at_index(panel, 4); } } @@ -120,10 +167,10 @@ function _setPanelStructs(state) { // workaround to force maximized windows to resize after removing affectsStruts // simulation of minimal swipe gesture to the opposite direction // todo - needs better solution!!!!!!!!!!! - /*const direction = _getAppGridAnimationDirection() === 2 ? 1 : -1; - Main.overview._swipeTracker._beginTouchSwipe(null, global.get_current_time(), 1, 1); - Main.overview._swipeTracker._updateGesture(null, global.get_current_time(), direction, 1); - GLib.timeout_add(0, 50, () => Main.overview._swipeTracker._endGesture(global.get_current_time(), 1, true));*/ + // const direction = _getAppGridAnimationDirection() === 2 ? 1 : -1; + // Main.overview._swipeTracker._beginTouchSwipe(null, global.get_current_time(), 1, 1); + // Main.overview._swipeTracker._updateGesture(null, global.get_current_time(), direction, 1); + // GLib.timeout_add(0, 50, () => Main.overview._swipeTracker._endGesture(global.get_current_time(), 1, true));*/ } function _showPanel(show = true) { @@ -134,9 +181,8 @@ function _showPanel(show = true) { translation_y: 0, onComplete: () => { _setPanelStructs(opt.PANEL_MODE === 0); - } + }, }); - } else { const panelHeight = Main.panel.height; Main.layoutManager.panelBox.ease({ @@ -145,34 +191,7 @@ function _showPanel(show = true) { onComplete: () => { Main.panel.opacity = 0; _setPanelStructs(opt.PANEL_MODE === 0); - } - }); - } -} - -function _connectPanel() { - // not reliable, disabled for now - /*if (!_panelEnterSigId) { - _panelEnterSigId = Main.panel.connect('enter-event', () => { - if (!Main.overview._shown) - _showPanel(true); + }, }); } - if (!_panelLeaveSigId) { - _panelLeaveSigId = Main.panel.connect('leave-event', () => { - if (!Main.overview._shown) - _showPanel(false); - }); - }*/ -} - -function _disconnectPanel() { - /*if (_panelEnterSigId) { - Main.panel.disconnect(_panelEnterSigId); - _panelEnterSigId = 0; - } - if (_panelLeaveSigId) { - Main.panel.disconnect(_panelLeaveSigId); - _panelLeaveSigId = 0; - }*/ } diff --git a/extensions/vertical-workspaces/recentFilesSearchProvider.js b/extensions/vertical-workspaces/lib/recentFilesSearchProvider.js index b94d696..86e38f4 100644 --- a/extensions/vertical-workspaces/recentFilesSearchProvider.js +++ b/extensions/vertical-workspaces/lib/recentFilesSearchProvider.js @@ -9,13 +9,16 @@ 'use strict'; -const { GLib, GObject, Gio, Gtk, Meta, St, Shell } = imports.gi; +const { GLib, Gio, Meta, St, Shell, Gtk } = imports.gi; const Main = imports.ui.main; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); -const Settings = Me.imports.settings; -const _ = Me.imports.settings._; +const Settings = Me.imports.lib.settings; +const _Util = Me.imports.lib.util; + +// gettext +const _ = Settings._; const shellVersion = Settings.shellVersion; @@ -31,24 +34,13 @@ var prefix = 'fq//'; var opt; -const Action = { - NONE: 0, - CLOSE: 1, - CLOSE_ALL: 2, - MOVE_TO_WS: 3, - MOVE_ALL_TO_WS: 4 -} - -function init() { -} - function getOverviewSearchResult() { - return Main.overview._overview.controls._searchController._searchResults; + return Main.overview._overview.controls._searchController._searchResults; } function update(reset = false) { - opt = Me.imports.settings.opt; + opt = Me.imports.lib.settings.opt; if (!reset && opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED && !recentFilesSearchProvider) { enable(); } else if (reset || !opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED) { @@ -63,7 +55,7 @@ function enable() { GLib.PRIORITY_DEFAULT, 2000, () => { - if (recentFilesSearchProvider == null) { + if (!recentFilesSearchProvider) { recentFilesSearchProvider = new RecentFilesSearchProvider(opt); getOverviewSearchResult()._registerProvider(recentFilesSearchProvider); } @@ -84,58 +76,6 @@ function disable() { } } -function fuzzyMatch(term, text) { - let pos = -1; - const matches = []; - // convert all accented chars to their basic form and to lower case - const _text = text;//.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); - const _term = term.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); - - // if term matches the substring exactly, gains the highest weight - if (_text.includes(_term)) { - return 0; - } - - for (let i = 0; i < _term.length; i++) { - let c = _term[i]; - let p; - if (pos > 0) - p = _term[i - 1]; - while (true) { - pos += 1; - if (pos >= _text.length) { - return -1; - } - if (_text[pos] == c) { - matches.push(pos); - break; - } else if (_text[pos] == p) { - matches.pop(); - matches.push(pos); - } - } - } - - // add all position to get a weight of the result - // results closer to the beginning of the text and term characters closer to each other will gain more weight. - return matches.reduce((r, p) => r + p) - matches.length * matches[0] + matches[0]; -} - -function strictMatch(term, text) { - // remove diacritics and accents from letters - let s = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); - let p = term.toLowerCase(); - let ps = p.split(/ +/); - - // allows to use multiple exact patterns separated by a space in arbitrary order - for (let w of ps) { // escape regex control chars - if (!s.match(w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) { - return -1; - } - } - return 0; -} - function makeResult(window, i) { const app = Shell.WindowTracker.get_default().get_window_app(window); const appName = app ? app.get_name() : 'Unknown'; @@ -143,13 +83,13 @@ function makeResult(window, i) { const wsIndex = window.get_workspace().index(); return { - 'id': i, - // convert all accented chars to their basic form and lower case for search - 'name': `${wsIndex + 1}: ${windowTitle} ${appName}`.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(), - 'appName': appName, - 'windowTitle': windowTitle, - 'window': window - } + 'id': i, + // convert all accented chars to their basic form and lower case for search + 'name': `${wsIndex + 1}: ${windowTitle} ${appName}`.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(), + appName, + windowTitle, + window, + }; } const closeSelectedRegex = /^\/x!$/; @@ -157,22 +97,22 @@ const closeAllResultsRegex = /^\/xa!$/; const moveToWsRegex = /^\/m[0-9]+$/; const moveAllToWsRegex = /^\/ma[0-9]+$/; -var RecentFilesSearchProvider = class RecentFilesSearchProvider { - constructor(gOptions) { - this._gOptions = gOptions; +const RecentFilesSearchProvider = class RecentFilesSearchProvider { + constructor() { + this.id = 'org.gnome.Nautilus.desktop'; this.appInfo = Gio.AppInfo.create_from_commandline('/usr/bin/nautilus -ws recent:///', 'Recent Files', null); - //this.appInfo = Shell.AppSystem.get_default().lookup_app('org.gnome.Nautilus.desktop').appInfo; + // this.appInfo = Shell.AppSystem.get_default().lookup_app('org.gnome.Nautilus.desktop').appInfo; this.appInfo.get_description = () => _('Search recent files'); this.appInfo.get_name = () => _('Recent Files'); - this.appInfo.get_id = () => 'org.gnome.Nautilus.desktop'; + this.appInfo.get_id = () => this.id; this.appInfo.get_icon = () => Gio.icon_new_for_string('document-open-recent-symbolic'); this.appInfo.should_show = () => true; - this.title = _('Recent Files Search Provider'), + this.canLaunchSearch = true; this.isRemoteProvider = false; } - _getResultSet (terms) { + _getResultSet(terms) { if (!terms[0].startsWith(prefix)) return []; // do not modify original terms @@ -182,60 +122,59 @@ var RecentFilesSearchProvider = class RecentFilesSearchProvider { const candidates = this.files; const _terms = [].concat(termsCopy); - let match; + // let match; const term = _terms.join(' '); - match = (s) => { + /* match = s => { return fuzzyMatch(term, s); - } + }; */ const results = []; let m; for (let id in candidates) { const file = this.files[id]; - const name = `${file.get_age()}d: ${file.get_display_name()} ${file.get_uri_display().replace(`\/${file.get_display_name()}`, '')}`; - if (this._gOptions.get('searchFuzzy')) { - m = fuzzyMatch(term, name); - } else { - m = strictMatch(term, name); - } - if (m !== -1) { - results.push({ weight: m, id: id}); - } + const name = `${file.get_age()}d: ${file.get_display_name()} ${file.get_uri_display().replace(`/${file.get_display_name()}`, '')}`; + if (opt.SEARCH_FUZZY) + m = _Util.fuzzyMatch(term, name); + else + m = _Util.strictMatch(term, name); + + if (m !== -1) + results.push({ weight: m, id }); } results.sort((a, b) => this.files[a.id].get_visited() < this.files[b.id].get_visited()); - this.resultIds = results.map((item) => item.id); + this.resultIds = results.map(item => item.id); return this.resultIds; } - getResultMetas (resultIds, callback = null) { - const metas = resultIds.map((id) => this.getResultMeta(id)); - if (shellVersion >= 43) { + getResultMetas(resultIds, callback = null) { + const metas = resultIds.map(id => this.getResultMeta(id)); + if (shellVersion >= 43) return new Promise(resolve => resolve(metas)); - } else { + else if (callback) callback(metas); - } + return null; } - getResultMeta (resultId) { + getResultMeta(resultId) { const result = this.files[resultId]; return { 'id': resultId, 'name': `${result.get_age()}: ${result.get_display_name()}`, - 'description': `${result.get_uri_display().replace(`\/${result.get_display_name()}`, '')}`, - 'createIcon': (size) => { + 'description': `${result.get_uri_display().replace(`/${result.get_display_name()}`, '')}`, + 'createIcon': size => { let icon = this.getIcon(result, size); return icon; }, - } + }; } getIcon(result, size) { let file = Gio.File.new_for_uri(result.get_uri()); let info = file.query_info(Gio.FILE_ATTRIBUTE_THUMBNAIL_PATH, - Gio.FileQueryInfoFlags.NONE, null); + Gio.FileQueryInfoFlags.NONE, null); let path = info.get_attribute_byte_string( Gio.FILE_ATTRIBUTE_THUMBNAIL_PATH); @@ -249,17 +188,17 @@ var RecentFilesSearchProvider = class RecentFilesSearchProvider { gicon = appInfo.get_icon(); } - if (gicon) { + if (gicon) icon = new St.Icon({ gicon, icon_size: size }); - } else { + else icon = new St.Icon({ icon_name: 'icon-missing', icon_size: size }); - } + return icon; } - launchSearch(terms, timeStamp) { - this._openNautilus('recent:///') + launchSearch(/* terms, timeStamp */) { + this._openNautilus('recent:///'); } _openNautilus(uri) { @@ -270,55 +209,52 @@ var RecentFilesSearchProvider = class RecentFilesSearchProvider { } } - activateResult (resultId, terms, timeStamp) { + activateResult(resultId /* , terms, timeStamp */) { const file = this.files[resultId]; - const [,,state] = global.get_pointer(); - //const isCtrlPressed = (state & ModifierType.CONTROL_MASK) != 0; - const isShiftPressed = (state & ModifierType.SHIFT_MASK) != 0; - - if (isShiftPressed) { + if (_Util.isShiftPressed()) { Main.overview.toggle(); this._openNautilus(file.get_uri()); } else { const appInfo = Gio.AppInfo.get_default_for_type(file.get_mime_type(), false); - if (!(appInfo && appInfo.launch_uris([file.get_uri()], null))) { + if (!(appInfo && appInfo.launch_uris([file.get_uri()], null))) this._openNautilus(file.get_uri()); - } } } - getInitialResultSet (terms, callback, cancellable = null) { - if (shellVersion >=43) { - cancellable = callback; - } + getInitialResultSet(terms, callback /* , cancellable = null*/) { + // In GS 43 callback arg has been removed + /* if (shellVersion >= 43) + cancellable = callback; */ const filesDict = {}; - const files = Gtk.RecentManager.get_default().get_items().filter((f)=> f.exists()); + const files = Gtk.RecentManager.get_default().get_items().filter(f => f.exists()); - for (let file of files) { + for (let file of files) filesDict[file.get_uri()] = file; - } + this.files = filesDict; - if (shellVersion >= 43) { + if (shellVersion >= 43) return new Promise(resolve => resolve(this._getResultSet(terms))); - } else { + else callback(this._getResultSet(terms)); - } + + return null; } - filterResults (results, maxResults) { - return results.slice(0, maxResults); + filterResults(results, maxResults) { + return results.slice(0, 20); + // return results.slice(0, maxResults); } - getSubsearchResultSet (previousResults, terms, callback, cancellable) { + getSubsearchResultSet(previousResults, terms, callback /* , cancellable*/) { // if we return previous results, quick typers get non-actual results callback(this._getResultSet(terms)); } - createResultOjbect(resultMeta) { + /* createResultObject(resultMeta) { return this.files[resultMeta.id]; - } -} + }*/ +}; diff --git a/extensions/vertical-workspaces/lib/search.js b/extensions/vertical-workspaces/lib/search.js new file mode 100644 index 0000000..8540626 --- /dev/null +++ b/extensions/vertical-workspaces/lib/search.js @@ -0,0 +1,206 @@ +/** + * V-Shell (Vertical Workspaces) + * search.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; +const { Shell, Gio, St, Clutter } = imports.gi; +const Main = imports.ui.main; + +const AppDisplay = imports.ui.appDisplay; +const Search = imports.ui.search; + +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const _Util = Me.imports.lib.util; + +const _ = Me.imports.lib.settings._; +const shellVersion = _Util.shellVersion; + +let opt; +let _overrides; +let _firstRun = true; + +let SEARCH_MAX_WIDTH; + +function update(reset = false) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('searchModule', true); + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled + if (_firstRun && reset) + return; + + _firstRun = false; + + if (_overrides) + _overrides.removeAll(); + + _updateSearchViewWidth(reset); + + if (reset) { + Main.overview._overview._controls.layoutManager._searchController.y_align = Clutter.ActorAlign.FILL; + opt = null; + _overrides = null; + return; + } + + _overrides = new _Util.Overrides(); + + _overrides.addOverride('AppSearchProvider', AppDisplay.AppSearchProvider.prototype, AppSearchProvider); + _overrides.addOverride('SearchResult', Search.SearchResult.prototype, SearchResult); + _overrides.addOverride('SearchResultsView', Search.SearchResultsView.prototype, SearchResultsView); + + // Don't expand the search view vertically and align it to the top + // this is important in the static workspace mode when the search view bg is not transparent + // also the "Searching..." and "No Results" notifications will be closer to the search entry, with the distance given by margin-top in the stylesheet + Main.overview._overview._controls.layoutManager._searchController.y_align = Clutter.ActorAlign.START; +} + +function _updateSearchViewWidth(reset = false) { + const searchContent = Main.overview._overview._controls.layoutManager._searchController._searchResults._content; + if (!SEARCH_MAX_WIDTH) { // just store original value; + const themeNode = searchContent.get_theme_node(); + const width = themeNode.get_max_width(); + SEARCH_MAX_WIDTH = width; + } + + if (reset) { + searchContent.set_style(''); + } else { + let width = Math.round(SEARCH_MAX_WIDTH * opt.SEARCH_VIEW_SCALE); + searchContent.set_style(`max-width: ${width}px;`); + } +} + +// AppDisplay.AppSearchProvider +const AppSearchProvider = { + getInitialResultSet(terms, callback, _cancellable) { + // Defer until the parental controls manager is initialized, so the + // results can be filtered correctly. + if (!this._parentalControlsManager.initialized) { + let initializedId = this._parentalControlsManager.connect('app-filter-changed', () => { + if (this._parentalControlsManager.initialized) { + this._parentalControlsManager.disconnect(initializedId); + this.getInitialResultSet(terms, callback, _cancellable); + } + }); + return; + } + + + const pattern = terms.join(' '); + let appInfoList = Shell.AppSystem.get_default().get_installed(); + + let weightList = {}; + appInfoList = appInfoList.filter(appInfo => { + try { + appInfo.get_id(); // catch invalid file encodings + } catch (e) { + return false; + } + + let string = ''; + let name; + let shouldShow = false; + if (appInfo.get_display_name) { + // show only launchers that should be visible in this DE + shouldShow = appInfo.should_show() && this._parentalControlsManager.shouldShowApp(appInfo); + + if (shouldShow) { + let dispName = appInfo.get_display_name() || ''; + let gName = appInfo.get_generic_name() || ''; + let description = appInfo.get_description() || ''; + let categories = appInfo.get_string('Categories') || ''; + let keywords = appInfo.get_string('Keywords') || ''; + name = dispName; + string = `${dispName} ${gName} ${description} ${categories} ${keywords}`; + } + } + + let m = -1; + if (shouldShow && opt.SEARCH_FUZZY) { + m = _Util.fuzzyMatch(pattern, name); + m = (m + _Util.strictMatch(pattern, string)) / 2; + } else if (shouldShow) { + m = _Util.strictMatch(pattern, string); + } + + if (m !== -1) + weightList[appInfo.get_id()] = m; + + return shouldShow && (m !== -1); + }); + + appInfoList.sort((a, b) => weightList[a.get_id()] > weightList[b.get_id()]); + + const usage = Shell.AppUsage.get_default(); + // sort apps by usage list + appInfoList.sort((a, b) => usage.compare(a.get_id(), b.get_id())); + // prefer apps where any word in their name starts with the pattern + appInfoList.sort((a, b) => _Util.isMoreRelevant(a.get_display_name(), b.get_display_name(), pattern)); + + let results = appInfoList.map(app => app.get_id()); + + results = results.concat(this._systemActions.getMatchingActions(terms)); + + if (shellVersion < 43) + callback(results); + else + return new Promise(resolve => resolve(results)); + }, + + // App search result size + createResultObject(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; + } + }, +}; + +const SearchResult = { + activate() { + this.provider.activateResult(this.metaInfo.id, this._resultsView.terms); + + if (this.metaInfo.clipboardText) { + St.Clipboard.get_default().set_text( + St.ClipboardType.CLIPBOARD, this.metaInfo.clipboardText); + } + // don't close overview if Shift key is pressed - Shift moves windows to the workspace + if (!_Util.isShiftPressed()) + Main.overview.toggle(); + }, +}; + +const SearchResultsView = { + _updateSearchProgress() { + let haveResults = this._providers.some(provider => { + let display = provider.display; + return display.getFirstResult() !== null; + }); + + this._scrollView.visible = haveResults; + this._statusBin.visible = !haveResults; + + if (!haveResults) { + if (this.searchInProgress) + this._statusText.set_text(_('Searching…')); + else + this._statusText.set_text(_('No results.')); + } + }, +}; diff --git a/extensions/vertical-workspaces/settings.js b/extensions/vertical-workspaces/lib/settings.js index feb285e..66f3a45 100644 --- a/extensions/vertical-workspaces/settings.js +++ b/extensions/vertical-workspaces/lib/settings.js @@ -1,5 +1,5 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * settings.js * * @author GdH <G-dH@github.com> @@ -11,10 +11,11 @@ const { GLib } = imports.gi; +const Config = imports.misc.config; + const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); -const Config = imports.misc.config; var shellVersion = parseFloat(Config.PACKAGE_VERSION); const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); @@ -24,7 +25,6 @@ const _schema = Me.metadata['settings-schema']; // common instance of Options accessible from all modules var opt; - var Options = class Options { constructor() { this._gsettings = ExtensionUtils.getSettings(_schema); @@ -37,7 +37,7 @@ var Options = class Options { this._writeTimeoutId = GLib.timeout_add( GLib.PRIORITY_DEFAULT, - 100, + 400, () => { this._gsettings.apply(); this._updateCachedSettings(); @@ -50,20 +50,25 @@ var Options = class Options { workspaceThumbnailsPosition: ['int', 'ws-thumbnails-position'], wsMaxSpacing: ['int', 'ws-max-spacing'], wsPreviewScale: ['int', 'ws-preview-scale'], - WsThumbnailsFull: ['bool', 'ws-thumbnails-full'], - secondaryWsThumbnailsPosition: ['int', 'secondary-ws-thumbnails-position'], + secWsPreviewScale: ['int', 'secondary-ws-preview-scale'], + secWsPreviewShift: ['bool', 'secondary-ws-preview-shift'], + wsThumbnailsFull: ['bool', 'ws-thumbnails-full'], + secWsThumbnailsPosition: ['int', 'secondary-ws-thumbnails-position'], dashPosition: ['int', 'dash-position'], dashPositionAdjust: ['int', 'dash-position-adjust'], wsTmbPositionAdjust: ['int', 'wst-position-adjust'], showWsTmbLabels: ['int', 'show-wst-labels'], showWsTmbLabelsOnHover: ['boolean', 'show-wst-labels-on-hover'], - SecWsTmbPositionAdjust: ['int', 'sec-wst-position-adjust'], + closeWsButtonMode: ['int', 'close-ws-button-mode'], + secWsTmbPositionAdjust: ['int', 'sec-wst-position-adjust'], dashMaxIconSize: ['int', 'dash-max-icon-size'], dashShowWindowsIcon: ['int', 'dash-show-windows-icon'], dashShowRecentFilesIcon: ['int', 'dash-show-recent-files-icon'], centerDashToWs: ['boolean', 'center-dash-to-ws'], showAppsIconPosition: ['int', 'show-app-icon-position'], wsThumbnailScale: ['int', 'ws-thumbnail-scale'], + wsThumbnailScaleAppGrid: ['int', 'ws-thumbnail-scale-appgrid'], + secWsThumbnailScale: ['int', 'secondary-ws-thumbnail-scale'], showSearchEntry: ['boolean', 'show-search-entry'], centerSearch: ['boolean', 'center-search'], centerAppGrid: ['boolean', 'center-app-grid'], @@ -72,7 +77,9 @@ var Options = class Options { enablePageShortcuts: ['boolean', 'enable-page-shortcuts'], showWsSwitcherBg: ['boolean', 'show-ws-switcher-bg'], showWsPreviewBg: ['boolean', 'show-ws-preview-bg'], + wsPreviewBgRadius: ['int', 'ws-preview-bg-radius'], showBgInOverview: ['boolean', 'show-bg-in-overview'], + overviewBgBrightness: ['int', 'overview-bg-brightness'], overviewBgBlurSigma: ['int', 'overview-bg-blur-sigma'], appGridBgBlurSigma: ['int', 'app-grid-bg-blur-sigma'], smoothBlurTransitions: ['boolean', 'smooth-blur-transitions'], @@ -82,33 +89,78 @@ var Options = class Options { animationSpeedFactor: ['int', 'animation-speed-factor'], fixUbuntuDock: ['boolean', 'fix-ubuntu-dock'], winPreviewIconSize: ['int', 'win-preview-icon-size'], - alwaysShowWinTitles: ['int', 'always-show-win-titles'], + alwaysShowWinTitles: ['boolean', 'always-show-win-titles'], startupState: ['int', 'startup-state'], overviewMode: ['int', 'overview-mode'], workspaceSwitcherAnimation: ['int', 'workspace-switcher-animation'], searchIconSize: ['int', 'search-icon-size'], searchViewScale: ['int', 'search-width-scale'], - appGridAllowCustom: ['int', 'app-grid-allow-custom'], appGridIconSize: ['int', 'app-grid-icon-size'], appGridColumns: ['int', 'app-grid-columns'], appGridRows: ['int', 'app-grid-rows'], appGridFolderIconSize: ['int', 'app-grid-folder-icon-size'], appGridFolderColumns: ['int', 'app-grid-folder-columns'], appGridFolderRows: ['int', 'app-grid-folder-rows'], - appGridIncludeDash: ['int', 'app-grid-include-dash'], + appGridFolderIconGrid: ['int', 'app-grid-folder-icon-grid'], + appGridContent: ['int', 'app-grid-content'], appGridIncompletePages: ['boolean', 'app-grid-incomplete-pages'], appGridOrder: ['int', 'app-grid-order'], + appGridNamesMode: ['int', 'app-grid-names'], + appGridActivePreview: ['boolean', 'app-grid-active-preview'], + appGridFolderCenter: ['boolean', 'app-grid-folder-center'], + appGridPageWidthScale: ['int', 'app-grid-page-width-scale'], + appGridSpacing: ['int', 'app-grid-spacing'], searchWindowsEnable: ['boolean', 'search-windows-enable'], searchRecentFilesEnable: ['boolean', 'search-recent-files-enable'], searchFuzzy: ['boolean', 'search-fuzzy'], searchMaxResultsRows: ['int', 'search-max-results-rows'], dashShowWindowsBeforeActivation: ['int', 'dash-show-windows-before-activation'], + dashIconScroll: ['int', 'dash-icon-scroll'], + searchWindowsIconScroll: ['int', 'search-windows-icon-scroll'], panelVisibility: ['int', 'panel-visibility'], panelPosition: ['int', 'panel-position'], - } + windowAttentionMode: ['int', 'window-attention-mode'], + wsSwPopupHPosition: ['int', 'ws-sw-popup-h-position'], + wsSwPopupVPosition: ['int', 'ws-sw-popup-v-position'], + wsSwPopupMode: ['int', 'ws-sw-popup-mode'], + favoritesNotify: ['int', 'favorites-notify'], + notificationPosition: ['int', 'notification-position'], + osdPosition: ['int', 'osd-position'], + hotCornerAction: ['int', 'hot-corner-action'], + hotCornerPosition: ['int', 'hot-corner-position'], + hotCornerFullscreen: ['boolean', 'hot-corner-fullscreen'], + hotCornerRipples: ['boolean', 'hot-corner-ripples'], + alwaysActivateSelectedWindow: ['boolean', 'always-activate-selected-window'], + windowIconClickSearch: ['boolean', 'window-icon-click-search'], + overlayKeySecondary: ['int', 'overlay-key-secondary'], + + workspaceThumbnailsModule: ['boolean', 'workspace-thumbnails-module'], + workspaceSwitcherPopupModule: ['boolean', 'workspace-switcher-popup-module'], + workspaceAnimationModule: ['boolean', 'workspace-animation-module'], + workspaceModule: ['boolean', 'workspace-module'], + windowManagerModule: ['boolean', 'window-manager-module'], + windowPreviewModule: ['boolean', 'window-preview-module'], + winAttentionHandlerModule: ['boolean', 'win-attention-handler-module'], + swipeTrackerModule: ['boolean', 'swipe-tracker-module'], + searchModule: ['boolean', 'search-module'], + panelModule: ['boolean', 'panel-module'], + overlayKeyModule: ['boolean', 'overlay-key-module'], + osdWindowModule: ['boolean', 'osd-window-module'], + messageTrayModule: ['boolean', 'message-tray-module'], + layoutModule: ['boolean', 'layout-module'], + dashModule: ['boolean', 'dash-module'], + appFavoritesModule: ['boolean', 'app-favorites-module'], + appDisplayModule: ['boolean', 'app-display-module'], + + profileName1: ['string', 'profile-name-1'], + profileName2: ['string', 'profile-name-2'], + profileName3: ['string', 'profile-name-3'], + profileName4: ['string', 'profile-name-4'], + }; this.cachedOptions = {}; this.shellVersion = shellVersion; + // this.storeProfile(0); } connect(name, callback) { @@ -125,24 +177,24 @@ var Options = class Options { } } - _updateCachedSettings(settings, key) { + _updateCachedSettings() { Object.keys(this.options).forEach(v => this.get(v, true)); } get(option, updateCache = false) { if (!this.options[option]) { log(`[${Me.metadata.name}] Error: Option ${option} is undefined.`); - return; + return null; } if (updateCache || this.cachedOptions[option] === undefined) { - const [format, key, settings] = this.options[option]; + const [, key, settings] = this.options[option]; let gSettings; - if (settings !== undefined) { + if (settings !== undefined) gSettings = settings(); - } else { + else gSettings = this._gsettings; - } + this.cachedOptions[option] = gSettings.get_value(key).deep_unpack(); } @@ -155,39 +207,74 @@ var Options = class Options { let gSettings = this._gsettings; - if (settings !== undefined) { + if (settings !== undefined) gSettings = settings(); - } + switch (format) { - case 'boolean': - gSettings.set_boolean(key, value); - break; - case 'int': - gSettings.set_int(key, value); - break; - case 'string': - gSettings.set_string(key, value); - break; - case 'strv': - gSettings.set_strv(key, value); - break; + case 'boolean': + gSettings.set_boolean(key, value); + break; + case 'int': + gSettings.set_int(key, value); + break; + case 'string': + gSettings.set_string(key, value); + break; + case 'strv': + gSettings.set_strv(key, value); + break; } } getDefault(option) { - const [format, key, settings] = this.options[option]; + const [, key, settings] = this.options[option]; let gSettings = this._gsettings; - if (settings !== undefined) { + if (settings !== undefined) gSettings = settings(); - } + return gSettings.get_default_value(key).deep_unpack(); } - _updateSettings(settings, key) { + storeProfile(index) { + const profile = {}; + Object.keys(this.options).forEach(v => { + profile[v] = this.get(v).toString(); + }); + + this._gsettings.set_value(`profile-data-${index}`, new GLib.Variant('a{ss}', profile)); + } + + loadProfile(index) { + const options = this._gsettings.get_value(`profile-data-${index}`).deep_unpack(); + this._gsettings.set_boolean('aaa-loading-profile', !this._gsettings.get_boolean('aaa-loading-profile')); + for (let o of Object.keys(options)) { + const [type] = this.options[o]; + let value = options[o]; + switch (type) { + case 'string': + break; + case 'boolean': + value = value === 'true'; + break; + case 'int': + value = parseInt(value); + break; + } + + this.set(o, value); + } + } + + resetProfile(index) { + this._gsettings.reset(`profile-data-${index}`); + this._gsettings.reset(`profile-name-${index}`); + } + + _updateSettings() { this.DASH_POSITION = this.get('dashPosition', true); this.DASH_TOP = this.DASH_POSITION === 0; this.DASH_RIGHT = this.DASH_POSITION === 1; @@ -197,6 +284,12 @@ var Options = class Options { this.DASH_VISIBLE = this.DASH_POSITION !== 4; // 4 - disable this.DASH_FOLLOW_RECENT_WIN = false; + this.DASH_CLICK_ACTION = this.get('dashShowWindowsBeforeActivation', true); + this.DASH_ICON_SCROLL = this.get('dashIconScroll', true); + this.DASH_SHIFT_CLICK_MV = true; + + this.SEARCH_WINDOWS_ICON_SCROLL = this.get('searchWindowsIconScroll', true); + this.DASH_POSITION_ADJUSTMENT = this.get('dashPositionAdjust', true); this.DASH_POSITION_ADJUSTMENT = this.DASH_POSITION_ADJUSTMENT * -1 / 100; // range 1 to -1 this.CENTER_DASH_WS = this.get('centerDashToWs', true); @@ -208,34 +301,44 @@ var Options = class Options { this.WS_TMB_POSITION = this.get('workspaceThumbnailsPosition', true); this.ORIENTATION = this.WS_TMB_POSITION > 4 ? 0 : 1; this.WORKSPACE_MAX_SPACING = this.get('wsMaxSpacing', true); - //ORIENTATION || DASH_LEFT || DASH_RIGHT ? 350 : 80; + // ORIENTATION || DASH_LEFT || DASH_RIGHT ? 350 : 80; this.SHOW_WS_TMB = ![4, 9].includes(this.WS_TMB_POSITION); // 4, 9 - disable - this.WS_TMB_FULL = this.get('WsThumbnailsFull', true); + this.WS_TMB_FULL = this.get('wsThumbnailsFull', true); // translate ws tmb position to 0 top, 1 right, 2 bottom, 3 left - //0L 1R, 2LF, 3RF, 4DV, 5T, 6B, 7TF, 8BF, 9DH + // 0L 1R, 2LF, 3RF, 4DV, 5T, 6B, 7TF, 8BF, 9DH this.WS_TMB_POSITION = [3, 1, 3, 1, 4, 0, 2, 0, 2, 8][this.WS_TMB_POSITION]; this.WS_TMB_TOP = this.WS_TMB_POSITION === 0; this.WS_TMB_RIGHT = this.WS_TMB_POSITION === 1; this.WS_TMB_BOTTOM = this.WS_TMB_POSITION === 2; this.WS_TMB_LEFT = this.WS_TMB_POSITION === 3; this.WS_TMB_POSITION_ADJUSTMENT = this.get('wsTmbPositionAdjust', true) * -1 / 100; // range 1 to -1 - this.SEC_WS_TMB_POSITION = this.get('secondaryWsThumbnailsPosition', true); + this.SEC_WS_TMB_POSITION = this.get('secWsThumbnailsPosition', true); + this.SHOW_SEC_WS_TMB = this.SEC_WS_TMB_POSITION !== 3 && this.SHOW_WS_TMB; this.SEC_WS_TMB_TOP = (this.SEC_WS_TMB_POSITION === 0 && !this.ORIENTATION) || (this.SEC_WS_TMB_POSITION === 2 && this.WS_TMB_TOP); this.SEC_WS_TMB_RIGHT = (this.SEC_WS_TMB_POSITION === 1 && this.ORIENTATION) || (this.SEC_WS_TMB_POSITION === 2 && this.WS_TMB_RIGHT); this.SEC_WS_TMB_BOTTOM = (this.SEC_WS_TMB_POSITION === 1 && !this.ORIENTATION) || (this.SEC_WS_TMB_POSITION === 2 && this.WS_TMB_BOTTOM); this.SEC_WS_TMB_LEFT = (this.SEC_WS_TMB_POSITION === 0 && this.ORIENTATION) || (this.SEC_WS_TMB_POSITION === 2 && this.WS_TMB_LEFT); - this.SEC_WS_TMB_POSITION_ADJUSTMENT = this.get('SecWsTmbPositionAdjust', true) * -1 / 100; // range 1 to -1 + this.SEC_WS_TMB_POSITION_ADJUSTMENT = this.get('secWsTmbPositionAdjust', true) * -1 / 100; // range 1 to -1 + this.SEC_WS_PREVIEW_SHIFT = this.get('secWsPreviewShift', true); this.SHOW_WST_LABELS = this.get('showWsTmbLabels', true); this.SHOW_WST_LABELS_ON_HOVER = this.get('showWsTmbLabelsOnHover', true); + this.CLOSE_WS_BUTTON_MODE = this.get('closeWsButtonMode', true); this.MAX_THUMBNAIL_SCALE = this.get('wsThumbnailScale', true) / 100; + this.MAX_THUMBNAIL_SCALE_APPGRID = this.get('wsThumbnailScaleAppGrid', true) / 100; + if (this.MAX_THUMBNAIL_SCALE_APPGRID === 0) + this.MAX_THUMBNAIL_SCALE_APPGRID = this.MAX_THUMBNAIL_SCALE; + this.MAX_THUMBNAIL_SCALE_STABLE = this.MAX_THUMBNAIL_SCALE === this.MAX_THUMBNAIL_SCALE_APPGRID; + this.SEC_MAX_THUMBNAIL_SCALE = this.get('secWsThumbnailScale', true) / 100; this.WS_PREVIEW_SCALE = this.get('wsPreviewScale', true) / 100; + this.SEC_WS_PREVIEW_SCALE = this.get('secWsPreviewScale', true) / 100; // calculate number of possibly visible neighbor previews according to ws scale - this.NUMBER_OF_VISIBLE_NEIGHBORS = Math.round(1 + (100 - this.WS_PREVIEW_SCALE) / 40); + this.NUMBER_OF_VISIBLE_NEIGHBORS = Math.round(1 + (1 - this.WS_PREVIEW_SCALE) / 4); this.SHOW_WS_TMB_BG = this.get('showWsSwitcherBg', true) && this.SHOW_WS_TMB; + this.WS_PREVIEW_BG_RADIUS = this.get('wsPreviewBgRadius', true); this.SHOW_WS_PREVIEW_BG = this.get('showWsPreviewBg', true); this.CENTER_APP_GRID = this.get('centerAppGrid', true); @@ -243,13 +346,13 @@ var Options = class Options { this.SHOW_SEARCH_ENTRY = this.get('showSearchEntry', true); this.CENTER_SEARCH_VIEW = this.get('centerSearch', true); this.APP_GRID_ANIMATION = this.get('appGridAnimation', true); - if (this.APP_GRID_ANIMATION === 4) { + if (this.APP_GRID_ANIMATION === 4) this.APP_GRID_ANIMATION = this._getAnimationDirection(); - } + this.SEARCH_VIEW_ANIMATION = this.get('searchViewAnimation', true); - if (this.SEARCH_VIEW_ANIMATION === 4) { + if (this.SEARCH_VIEW_ANIMATION === 4) this.SEARCH_VIEW_ANIMATION = 3; - } + this.WS_ANIMATION = this.get('workspaceAnimation', true); this.WIN_PREVIEW_ICON_SIZE = [64, 48, 32, 22, 8][this.get('winPreviewIconSize', true)]; @@ -257,6 +360,7 @@ var Options = class Options { this.STARTUP_STATE = this.get('startupState', true); this.SHOW_BG_IN_OVERVIEW = this.get('showBgInOverview', true); + this.OVERVIEW_BG_BRIGHTNESS = this.get('overviewBgBrightness', true) / 100; this.OVERVIEW_BG_BLUR_SIGMA = this.get('overviewBgBlurSigma', true); this.APP_GRID_BG_BLUR_SIGMA = this.get('appGridBgBlurSigma', true); this.SMOOTH_BLUR_TRANSITIONS = this.get('smoothBlurTransitions', true); @@ -264,47 +368,102 @@ var Options = class Options { this.OVERVIEW_MODE = this.get('overviewMode', true); this.OVERVIEW_MODE2 = this.OVERVIEW_MODE === 2; this.WORKSPACE_MODE = this.OVERVIEW_MODE ? 0 : 1; - //Workspace.WINDOW_PREVIEW_MAXIMUM_SCALE = 0.95; this.STATIC_WS_SWITCHER_BG = this.get('workspaceSwitcherAnimation', true); this.ANIMATION_TIME_FACTOR = this.get('animationSpeedFactor', true) / 100; - //St.Settings.get().slow_down_factor = this.ANIMATION_TIME_FACTOR; this.SEARCH_ICON_SIZE = this.get('searchIconSize', true); this.SEARCH_VIEW_SCALE = this.get('searchViewScale', true) / 100; this.SEARCH_MAX_ROWS = this.get('searchMaxResultsRows', true); - //imports.ui.search.MAX_LIST_SEARCH_RESULTS_ROWS = this.SEARCH_MAX_ROWS; + this.SEARCH_FUZZY = this.get('searchFuzzy', true); - this.APP_GRID_ALLOW_INCOMPLETE_PAGES = false; - this.APP_GRID_ALLOW_CUSTOM = this.get('appGridAllowCustom', true); + this.APP_GRID_ALLOW_INCOMPLETE_PAGES = this.get('appGridIncompletePages', true); this.APP_GRID_ICON_SIZE = this.get('appGridIconSize', true); this.APP_GRID_COLUMNS = this.get('appGridColumns', true); this.APP_GRID_ROWS = this.get('appGridRows', true); + this.APP_GRID_ADAPTIVE = !this.APP_GRID_COLUMNS && !this.APP_GRID_ROWS; this.APP_GRID_ORDER = this.get('appGridOrder', true); - this.APP_GRID_INCLUDE_DASH = this.get('appGridIncludeDash', true); - + + this.APP_GRID_INCLUDE_DASH = this.get('appGridContent', true); + /* APP_GRID_INCLUDE_DASH + 0 - Include All + 1 - Include All - Favorites and Runnings First + 2 - Exclude Favorites (Default) + 3 - Exclude Running + 4 - Exclude Favorites and Running + */ + this.APP_GRID_EXCLUDE_FAVORITES = this.APP_GRID_INCLUDE_DASH === 2 || this.APP_GRID_INCLUDE_DASH === 4; + this.APP_GRID_EXCLUDE_RUNNING = this.APP_GRID_INCLUDE_DASH === 3 || this.APP_GRID_INCLUDE_DASH === 4; + this.APP_GRID_DASH_FIRST = this.APP_GRID_INCLUDE_DASH === 1; + + this.APP_GRID_NAMES_MODE = this.get('appGridNamesMode', true); + this.APP_GRID_FOLDER_ICON_SIZE = this.get('appGridFolderIconSize', true); + this.APP_GRID_FOLDER_ICON_GRID = this.get('appGridFolderIconGrid', true); this.APP_GRID_FOLDER_COLUMNS = this.get('appGridFolderColumns', true); this.APP_GRID_FOLDER_ROWS = this.get('appGridFolderRows', true); + this.APP_GRID_SPACING = this.get('appGridSpacing', true); + this.APP_GRID_FOLDER_DEFAULT = this.APP_GRID_FOLDER_ROWS === 3 && this.APP_GRID_FOLDER_COLUMNS === 3; + this.APP_GRID_ACTIVE_PREVIEW = this.get('appGridActivePreview', true); + this.APP_GRID_FOLDER_CENTER = this.get('appGridFolderCenter', true); + this.APP_GRID_PAGE_WIDTH_SCALE = this.get('appGridPageWidthScale', true) / 100; - this.DASH_SHOW_WINS_BEFORE = this.get('dashShowWindowsBeforeActivation', true); - this.DASH_SHIFT_CLICK_MV = true; + this.APP_GRID_ICON_SIZE_DEFAULT = this.APP_GRID_ACTIVE_PREVIEW && !this.APP_GRID_ORDER ? 176 : 96; + this.APP_GRID_FOLDER_ICON_SIZE_DEFAULT = 96; this.WINDOW_SEARCH_PROVIDER_ENABLED = this.get('searchWindowsEnable', true); this.RECENT_FILES_SEARCH_PROVIDER_ENABLED = this.get('searchRecentFilesEnable', true); this.PANEL_POSITION_TOP = this.get('panelPosition', true) === 0; this.PANEL_MODE = this.get('panelVisibility', true); + this.PANEL_DISABLED = this.PANEL_MODE === 2; + this.PANEL_OVERVIEW_ONLY = this.PANEL_MODE === 1; this.START_Y_OFFSET = 0; // set from main module + this.FIX_UBUNTU_DOCK = this.get('fixUbuntuDock', true); + + this.WINDOW_ATTENTION_MODE = this.get('windowAttentionMode', true); + this.WINDOW_ATTENTION_DISABLE_NOTIFICATIONS = this.WINDOW_ATTENTION_MODE === 1; + this.WINDOW_ATTENTION_FOCUS_IMMEDIATELY = this.WINDOW_ATTENTION_MODE === 2; + + this.WS_SW_POPUP_H_POSITION = this.get('wsSwPopupHPosition', true) / 100; + this.WS_SW_POPUP_V_POSITION = this.get('wsSwPopupVPosition', true) / 100; + this.WS_SW_POPUP_MODE = this.get('wsSwPopupMode', true); + + this.SHOW_FAV_NOTIFICATION = this.get('favoritesNotify', true); + this.NOTIFICATION_POSITION = this.get('notificationPosition', true); + + this.OSD_POSITION = this.get('osdPosition', true); + + this.HOT_CORNER_ACTION = this.get('hotCornerAction', true); + this.HOT_CORNER_POSITION = this.get('hotCornerPosition', true); + if (this.HOT_CORNER_POSITION === 6 && this.DASH_VISIBLE) + this.HOT_CORNER_EDGE = true; + else + this.HOT_CORNER_EDGE = false; + if ([5, 6].includes(this.HOT_CORNER_POSITION)) { + if (this.DASH_TOP || this.DASH_LEFT) + this.HOT_CORNER_POSITION = 1; + else if (this.DASH_RIGHT) + this.HOT_CORNER_POSITION = 2; + else if (this.DASH_BOTTOM) + this.HOT_CORNER_POSITION = 3; + else + this.HOT_CORNER_POSITION = 0; + } + this.HOT_CORNER_FULLSCREEN = this.get('hotCornerFullscreen', true); + this.HOT_CORNER_RIPPLES = this.get('hotCornerRipples', true); + + this.ALWAYS_ACTIVATE_SELECTED_WINDOW = this.get('alwaysActivateSelectedWindow', true); + this.WINDOW_ICON_CLICK_SEARCH = this.get('windowIconClickSearch', true); + + this.OVERLAY_KEY_SECONDARY = this.get('overlayKeySecondary', true); } _getAnimationDirection() { - if (this.ORIENTATION) { - return (this.WS_TMB_LEFT || !this.SHOW_WS_TMB) ? 1 : 2; // 1 right, 2 left - } else { - return (this.WS_TMB_TOP || !this.SHOW_WS_TMB) ? 3 : 5; // 3 bottom, 5 top - } + if (this.ORIENTATION) + return this.WS_TMB_LEFT || !this.SHOW_WS_TMB ? 1 : 2; // 1 right, 2 left + else + return this.WS_TMB_TOP || !this.SHOW_WS_TMB ? 3 : 5; // 3 bottom, 5 top } - }; diff --git a/extensions/vertical-workspaces/swipeTracker.js b/extensions/vertical-workspaces/lib/swipeTracker.js index d5c52da..d9c3407 100644 --- a/extensions/vertical-workspaces/swipeTracker.js +++ b/extensions/vertical-workspaces/lib/swipeTracker.js @@ -1,7 +1,7 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * swipeTracker.js - * + * * @author GdH <G-dH@github.com> * @copyright 2022 - 2023 * @license GPL-3.0 @@ -15,13 +15,23 @@ const Main = imports.ui.main; const SwipeTracker = imports.ui.swipeTracker; const Me = imports.misc.extensionUtils.getCurrentExtension(); + let opt; +let _firstRun = true; let _vwGestureUpdateId; let _originalGestureUpdateId; function update(reset = false) { - opt = Me.imports.settings.opt; + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('swipeTrackerModule', true); + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled + if (_firstRun && reset) + return; + + _firstRun = false; if (reset || !opt.ORIENTATION) { // 1-VERTICAL, 0-HORIZONTAL // original swipeTrackers' orientation and updateGesture function @@ -56,11 +66,9 @@ function update(reset = false) { } } -//---- SwipeTracker ----------------------------------------------------------------------------------- -// switch overview's state gesture direction -var SwipeTrackerVertical = { - _updateGesture: function(gesture, time, delta, distance) { - if (this._state !== 1) //State.SCROLLING) +const SwipeTrackerVertical = { + _updateGesture(gesture, time, delta, distance) { + if (this._state !== 1) // State.SCROLLING) return; if ((this._allowedModes & Main.actionMode) === 0 || !this.enabled) { @@ -75,5 +83,5 @@ var SwipeTrackerVertical = { this._progress = Math.clamp(this._progress, ...this._getBounds(this._initialProgress)); this.emit('update', this._progress); - } -} + }, +}; diff --git a/extensions/vertical-workspaces/lib/util.js b/extensions/vertical-workspaces/lib/util.js new file mode 100644 index 0000000..5f5c069 --- /dev/null +++ b/extensions/vertical-workspaces/lib/util.js @@ -0,0 +1,364 @@ +/** + * V-Shell (Vertical Workspaces) + * util.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; + +const Gi = imports._gi; +const { Shell, Meta, Clutter } = imports.gi; + +const Config = imports.misc.config; +const Main = imports.ui.main; + +const ExtensionUtils = imports.misc.extensionUtils; +const Me = ExtensionUtils.getCurrentExtension(); + +var shellVersion = parseFloat(Config.PACKAGE_VERSION); + +var Overrides = class { + constructor() { + this._overrides = {}; + } + + addOverride(name, prototype, overrideList) { + this._overrides[name] = { + originals: this.overrideProto(prototype, overrideList), + prototype, + }; + } + + removeOverride(name) { + const override = this._overrides[name]; + if (!override) + return false; + + this.overrideProto(override.prototype, override.originals); + this._overrides[name] = undefined; + return true; + } + + removeAll() { + for (let name in this._overrides) { + this.removeOverride(name); + this._overrides[name] = undefined; + } + } + + hookVfunc(proto, symbol, func) { + proto[Gi.hook_up_vfunc_symbol](symbol, func); + } + + overrideProto(proto, overrides) { + const backup = {}; + + for (let symbol in overrides) { + if (symbol.startsWith('after_')) { + const actualSymbol = symbol.slice('after_'.length); + const fn = proto[actualSymbol]; + const afterFn = overrides[symbol]; + proto[actualSymbol] = function (...args) { + args = Array.prototype.slice.call(args); + const res = fn.apply(this, args); + afterFn.apply(this, args); + return res; + }; + backup[actualSymbol] = fn; + } else { + backup[symbol] = proto[symbol]; + if (symbol.startsWith('vfunc')) { + if (shellVersion < 42) + this.hookVfunc(proto, symbol.slice(6), overrides[symbol]); + else + this.hookVfunc(proto[Gi.gobject_prototype_symbol], symbol.slice(6), overrides[symbol]); + } else { + proto[symbol] = overrides[symbol]; + } + } + } + return backup; + } +}; + +function getOverviewTranslations(opt, dash, tmbBox, searchEntryBin) { + // const tmbBox = Main.overview._overview._controls._thumbnailsBox; + let searchTranslationY = 0; + if (searchEntryBin.visible) { + const offset = (dash.visible && (!opt.DASH_VERTICAL ? dash.height + 12 : 0)) + + (opt.WS_TMB_TOP ? tmbBox.height + 12 : 0); + searchTranslationY = -searchEntryBin.height - offset - 30; + } + + let tmbTranslationX = 0; + let tmbTranslationY = 0; + let offset; + if (tmbBox.visible) { + switch (opt.WS_TMB_POSITION) { + case 3: // left + offset = 10 + (dash?.visible && opt.DASH_LEFT ? dash.width : 0); + tmbTranslationX = -tmbBox.width - offset; + tmbTranslationY = 0; + break; + case 1: // right + offset = 10 + (dash?.visible && opt.DASH_RIGHT ? dash.width : 0); + tmbTranslationX = tmbBox.width + offset; + tmbTranslationY = 0; + break; + case 0: // top + offset = 10 + (dash?.visible && opt.DASH_TOP ? dash.height : 0) + Main.panel.height; + tmbTranslationX = 0; + tmbTranslationY = -tmbBox.height - offset; + break; + case 2: // bottom + offset = 10 + (dash?.visible && opt.DASH_BOTTOM ? dash.height : 0) + Main.panel.height; // just for case the panel is at bottom + tmbTranslationX = 0; + tmbTranslationY = tmbBox.height + offset; + break; + } + } + + let dashTranslationX = 0; + let dashTranslationY = 0; + let position = opt.DASH_POSITION; + // if DtD replaced the original Dash, read its position + if (dashIsDashToDock()) + position = dash._position; + + if (dash?.visible) { + switch (position) { + case 0: // top + dashTranslationX = 0; + dashTranslationY = -dash.height - dash.margin_bottom - Main.panel.height; + break; + case 1: // right + dashTranslationX = dash.width; + dashTranslationY = 0; + break; + case 2: // bottom + dashTranslationX = 0; + dashTranslationY = dash.height + dash.margin_bottom + Main.panel.height; + break; + case 3: // left + dashTranslationX = -dash.width; + dashTranslationY = 0; + break; + } + } + + return [tmbTranslationX, tmbTranslationY, dashTranslationX, dashTranslationY, searchTranslationY]; +} + +function openPreferences() { + const windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL, null); + let tracker = Shell.WindowTracker.get_default(); + let metaWin, isVW = null; + + for (let win of windows) { + const app = tracker.get_window_app(win); + if (win.get_title().includes(Me.metadata.name) && app.get_name() === 'Extensions') { + // this is our existing window + metaWin = win; + isVW = true; + break; + } else if (win.wm_class.includes('org.gnome.Shell.Extensions')) { + // this is prefs window of another extension + metaWin = win; + isVW = false; + } + } + + if (metaWin && !isVW) { + // other prefs window blocks opening another prefs window, so close it + metaWin.delete(global.get_current_time()); + } else if (metaWin && isVW) { + // if prefs window already exist, move it to the current WS and activate it + metaWin.change_workspace(global.workspace_manager.get_active_workspace()); + metaWin.activate(global.get_current_time()); + } + + if (!metaWin || (metaWin && !isVW)) { + try { + Main.extensionManager.openExtensionPrefs(Me.metadata.uuid, '', {}); + } catch (e) { + log(e); + } + } +} + +function activateSearchProvider(prefix = '') { + const searchEntry = Main.overview.searchEntry; + if (!searchEntry.get_text() || !searchEntry.get_text().startsWith(prefix)) { + prefix = `${prefix} `; + const position = prefix.length; + searchEntry.set_text(prefix); + searchEntry.get_first_child().set_cursor_position(position); + searchEntry.get_first_child().set_selection(position, position); + } else { + searchEntry.set_text(''); + } +} + +function dashNotDefault() { + return Main.overview.dash !== Main.overview._overview._controls.layoutManager._dash; +} + +function dashIsDashToDock() { + return Main.overview.dash._isHorizontal !== undefined; +} + +// Reorder Workspaces - callback for Dash and workspacesDisplay +function reorderWorkspace(direction = 0) { + let activeWs = global.workspace_manager.get_active_workspace(); + let activeWsIdx = activeWs.index(); + let targetIdx = activeWsIdx + direction; + if (targetIdx > -1 && targetIdx < global.workspace_manager.get_n_workspaces()) + global.workspace_manager.reorder_workspace(activeWs, targetIdx); +} + +function exposeWindows(adjustment, activateKeyboard) { + // expose windows for static overview modes + if (!adjustment.value && !Main.overview._animationInProgress) { + if (adjustment.value === 0) { + adjustment.value = 0; + adjustment.ease(1, { + duration: 200, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + if (activateKeyboard) { + Main.ctrlAltTabManager._items.forEach(i => { + if (i.sortGroup === 1 && i.name === 'Windows') + Main.ctrlAltTabManager.focusGroup(i); + }); + } + }, + }); + } + } +} + +function isShiftPressed(state = null) { + if (state === null) + [,, state] = global.get_pointer(); + return (state & Clutter.ModifierType.SHIFT_MASK) !== 0; +} + +function isCtrlPressed(state = null) { + if (state === null) + [,, state] = global.get_pointer(); + return (state & Clutter.ModifierType.CONTROL_MASK) !== 0; +} + +function isAltPressed(state = null) { + if (state === null) + [,, state] = global.get_pointer(); + return (state & Clutter.ModifierType.MOD1_MASK) !== 0; +} + +function fuzzyMatch(term, text) { + let pos = -1; + const matches = []; + // convert all accented chars to their basic form and to lower case + const _text = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); + const _term = term.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); + + // if term matches the substring exactly, gains the highest weight + if (_text.includes(_term)) + return 0; + + for (let i = 0; i < _term.length; i++) { + let c = _term[i]; + let p; + if (pos > 0) + p = _term[i - 1]; + while (true) { + pos += 1; + if (pos >= _text.length) + return -1; + + if (_text[pos] === c) { + matches.push(pos); + break; + } else if (_text[pos] === p) { + matches.pop(); + matches.push(pos); + } + } + } + + // add all position to get a weight of the result + // results closer to the beginning of the text and term characters closer to each other will gain more weight. + return matches.reduce((r, p) => r + p) - matches.length * matches[0] + matches[0]; +} + +function strictMatch(term, text) { + // remove diacritics and accents from letters + let s = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); + let p = term.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); + let ps = p.split(/ +/); + + // allows to use multiple exact patterns separated by a space in arbitrary order + for (let w of ps) { // escape regex control chars + if (!s.match(w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) + return -1; + } + return 0; +} + +function isMoreRelevant(stringA, stringB, pattern) { + let regex = /[^a-zA-Z\d]/; + let strSplitA = stringA.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().split(regex); + let strSplitB = stringB.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().split(regex); + let aAny = false; + strSplitA.forEach(w => { + aAny = aAny || w.startsWith(pattern); + }); + let bAny = false; + strSplitB.forEach(w => { + bAny = bAny || w.startsWith(pattern); + }); + + // if both strings contain a word that starts with the pattern + // prefer the one whose first word starts with the pattern + if (aAny && bAny) + return !strSplitA[0].startsWith(pattern) && strSplitB[0].startsWith(pattern); + else + return !aAny && bAny; +} + +function getEnabledExtensions(uuid = '') { + let extensions = []; + Main.extensionManager._extensions.forEach(e => { + if (e.state === 1 && e.uuid.includes(uuid)) + extensions.push(e); + }); + return extensions; +} + +function getScrollDirection(event) { + // scroll wheel provides two types of direction information: + // 1. Clutter.ScrollDirection.DOWN / Clutter.ScrollDirection.UP + // 2. Clutter.ScrollDirection.SMOOTH + event.get_scroll_delta() + // first SMOOTH event returns 0 delta, + // so we need to always read event.direction + // since mouse without smooth scrolling provides exactly one SMOOTH event on one wheel rotation click + // on the other hand, under X11, one wheel rotation click sometimes doesn't send direction event, only several SMOOTH events + // so we also need to convert the delta to direction + let direction = event.get_scroll_direction(); + + if (direction !== Clutter.ScrollDirection.SMOOTH) + return direction; + + let [, delta] = event.get_scroll_delta(); + + if (!delta) + return null; + + direction = delta > 0 ? Clutter.ScrollDirection.DOWN : Clutter.ScrollDirection.UP; + + return direction; +} diff --git a/extensions/vertical-workspaces/lib/windowAttentionHandler.js b/extensions/vertical-workspaces/lib/windowAttentionHandler.js new file mode 100644 index 0000000..10703c2 --- /dev/null +++ b/extensions/vertical-workspaces/lib/windowAttentionHandler.js @@ -0,0 +1,90 @@ +/** + * V-Shell (Vertical Workspaces) + * windowAttentionHandler.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; + +const Main = imports.ui.main; +const WindowAttentionHandler = imports.ui.windowAttentionHandler; +const MessageTray = imports.ui.messageTray; +const ExtensionUtils = imports.misc.extensionUtils; +const Me = ExtensionUtils.getCurrentExtension(); + +const _Util = Me.imports.lib.util; + +let opt; +let _firstRun = false; + +function update(reset = false) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('winAttentionHandlerModule', true); + reset = reset || !moduleEnabled; + + if (_firstRun && reset) + return; + + _firstRun = false; + if (reset) { + reset = true; + _updateConnections(reset); + opt = null; + return; + } + + _updateConnections(); +} + +function _updateConnections(reset) { + global.display.disconnectObject(Main.windowAttentionHandler); + + const handlerFnc = reset + ? Main.windowAttentionHandler._onWindowDemandsAttention + : WindowAttentionHandlerCommon._onWindowDemandsAttention; + + global.display.connectObject( + 'window-demands-attention', handlerFnc.bind(Main.windowAttentionHandler), + 'window-marked-urgent', handlerFnc.bind(Main.windowAttentionHandler), + Main.windowAttentionHandler); +} + +const WindowAttentionHandlerCommon = { + _onWindowDemandsAttention(display, window) { + // Deny attention notifications if the App Grid is open, to avoid notification spree when opening a folder + if (Main.overview._shown && Main.overview.dash.showAppsButton.checked) { + return; + } else if (opt.WINDOW_ATTENTION_FOCUS_IMMEDIATELY) { + if (!Main.overview._shown) + Main.activateWindow(window); + return; + } + + const app = this._tracker.get_window_app(window); + const source = new WindowAttentionHandler.WindowAttentionSource(app, window); + Main.messageTray.add(source); + + let [title, banner] = this._getTitleAndBanner(app, window); + + const notification = new MessageTray.Notification(source, title, banner); + notification.connect('activated', () => { + source.open(); + }); + notification.setForFeedback(true); + + if (opt.WINDOW_ATTENTION_DISABLE_NOTIFICATIONS) + // just push the notification to the message tray without showing notification + source.pushNotification(notification); + else + source.showNotification(notification); + + window.connectObject('notify::title', () => { + [title, banner] = this._getTitleAndBanner(app, window); + notification.update(title, banner); + }, source); + }, +}; diff --git a/extensions/vertical-workspaces/windowManager.js b/extensions/vertical-workspaces/lib/windowManager.js index e931a67..2d46b0b 100644 --- a/extensions/vertical-workspaces/windowManager.js +++ b/extensions/vertical-workspaces/lib/windowManager.js @@ -1,7 +1,7 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * windowManager.js - * + * * @author GdH <G-dH@github.com> * @copyright 2022 - 2023 * @license GPL-3.0 @@ -10,40 +10,50 @@ 'use strict'; -const { GObject, Clutter, Meta, Shell } = imports.gi; +const { GObject, Clutter, Meta } = imports.gi; const Main = imports.ui.main; const WindowManager = imports.ui.windowManager; const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.util; +const _Util = Me.imports.lib.util; let _overrides; const MINIMIZE_WINDOW_ANIMATION_TIME = WindowManager.MINIMIZE_WINDOW_ANIMATION_TIME; const MINIMIZE_WINDOW_ANIMATION_MODE = WindowManager.MINIMIZE_WINDOW_ANIMATION_MODE; let opt; +let _firstRun = true; function update(reset = false) { - if (_overrides) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('windowManagerModule', true); + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled + if (_firstRun && reset) + return; + + _firstRun = false; + + if (_overrides) _overrides.removeAll(); - } + _replaceMinimizeFunction(reset); + if (reset) { _overrides = null; opt = null; return; } - opt = Me.imports.settings.opt; - _overrides = new _Util.Overrides(); _overrides.addOverride('WindowManager', WindowManager.WindowManager.prototype, WindowManagerCommon); } -//------------- Fix and adapt minimize/unminimize animations -------------------------------------- +// ------------- Fix and adapt minimize/unminimize animations -------------------------------------- let _originalMinimizeSigId; let _minimizeSigId; @@ -61,7 +71,6 @@ function _replaceMinimizeFunction(reset = false) { _unminimizeSigId = 0; Main.wm._shellwm.unblock_signal_handler(_originalUnminimizeSigId); _originalUnminimizeSigId = 0; - } else if (!_minimizeSigId) { _originalMinimizeSigId = GObject.signal_handler_find(Main.wm._shellwm, { signalId: 'minimize' }); if (_originalMinimizeSigId) { @@ -82,7 +91,7 @@ function _replaceMinimizeFunction(reset = false) { // anyway, animation is better, even if the Activities button is not visible... // and also add support for bottom position of the panel const WindowManagerCommon = { - _minimizeWindow: function(shellwm, actor) { + _minimizeWindow(shellwm, actor) { const types = [ Meta.WindowType.NORMAL, Meta.WindowType.MODAL_DIALOG, @@ -97,48 +106,48 @@ const WindowManagerCommon = { this._minimizing.add(actor); - if (false/*actor.meta_window.is_monitor_sized()*/) { + /* if (actor.meta_window.is_monitor_sized()) { actor.get_first_child().ease({ opacity: 0, duration: MINIMIZE_WINDOW_ANIMATION_TIME, mode: MINIMIZE_WINDOW_ANIMATION_MODE, onStopped: () => this._minimizeWindowDone(shellwm, actor), }); + } else { */ + let xDest, yDest, xScale, yScale; + let [success, geom] = actor.meta_window.get_icon_geometry(); + if (success) { + xDest = geom.x; + yDest = geom.y; + xScale = geom.width / actor.width; + yScale = geom.height / actor.height; } else { - let xDest, yDest, xScale, yScale; - let [success, geom] = actor.meta_window.get_icon_geometry(); - if (success) { - xDest = geom.x; - yDest = geom.y; - xScale = geom.width / actor.width; - yScale = geom.height / actor.height; - } else { - let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()]; - if (!monitor) { - this._minimizeWindowDone(); - return; - } - xDest = monitor.x; - yDest = opt.PANEL_POSITION_TOP ? monitor.y : monitor.y + monitor.height; - if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) - xDest += monitor.width; - xScale = 0; - yScale = 0; + let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()]; + if (!monitor) { + this._minimizeWindowDone(); + return; } - - actor.ease({ - scale_x: xScale, - scale_y: yScale, - x: xDest, - y: yDest, - duration: MINIMIZE_WINDOW_ANIMATION_TIME, - mode: MINIMIZE_WINDOW_ANIMATION_MODE, - onStopped: () => this._minimizeWindowDone(shellwm, actor), - }); + xDest = monitor.x; + yDest = opt.PANEL_POSITION_TOP ? monitor.y : monitor.y + monitor.height; + if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL) + xDest += monitor.width; + xScale = 0; + yScale = 0; } + + actor.ease({ + scale_x: xScale, + scale_y: yScale, + x: xDest, + y: yDest, + duration: MINIMIZE_WINDOW_ANIMATION_TIME, + mode: MINIMIZE_WINDOW_ANIMATION_MODE, + onStopped: () => this._minimizeWindowDone(shellwm, actor), + }); + // } }, - _minimizeWindowDone: function(shellwm, actor) { + _minimizeWindowDone(shellwm, actor) { if (this._minimizing.delete(actor)) { actor.remove_all_transitions(); actor.set_scale(1.0, 1.0); @@ -149,7 +158,7 @@ const WindowManagerCommon = { } }, - _unminimizeWindow: function(shellwm, actor) { + _unminimizeWindow(shellwm, actor) { const types = [ Meta.WindowType.NORMAL, Meta.WindowType.MODAL_DIALOG, @@ -162,7 +171,7 @@ const WindowManagerCommon = { this._unminimizing.add(actor); - if (false/*actor.meta_window.is_monitor_sized()*/) { + /* if (false/* actor.meta_window.is_monitor_sized()) { actor.opacity = 0; actor.set_scale(1.0, 1.0); actor.ease({ @@ -171,38 +180,38 @@ const WindowManagerCommon = { mode: MINIMIZE_WINDOW_ANIMATION_MODE, onStopped: () => this._unminimizeWindowDone(shellwm, actor), }); + } else { */ + let [success, geom] = actor.meta_window.get_icon_geometry(); + if (success) { + actor.set_position(geom.x, geom.y); + actor.set_scale(geom.width / actor.width, + geom.height / actor.height); } else { - let [success, geom] = actor.meta_window.get_icon_geometry(); - if (success) { - actor.set_position(geom.x, geom.y); - actor.set_scale(geom.width / actor.width, - geom.height / actor.height); - } else { - let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()]; - if (!monitor) { - actor.show(); - this._unminimizeWindowDone(); - return; - } - actor.set_position(monitor.x, opt.PANEL_POSITION_TOP ? monitor.y : monitor.y + monitor.height); - if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) - actor.x += monitor.width; - actor.set_scale(0, 0); + let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()]; + if (!monitor) { + actor.show(); + this._unminimizeWindowDone(); + return; } - - let rect = actor.meta_window.get_buffer_rect(); - let [xDest, yDest] = [rect.x, rect.y]; - - actor.show(); - actor.ease({ - scale_x: 1, - scale_y: 1, - x: xDest, - y: yDest, - duration: MINIMIZE_WINDOW_ANIMATION_TIME, - mode: MINIMIZE_WINDOW_ANIMATION_MODE, - onStopped: () => this._unminimizeWindowDone(shellwm, actor), - }); + actor.set_position(monitor.x, opt.PANEL_POSITION_TOP ? monitor.y : monitor.y + monitor.height); + if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL) + actor.x += monitor.width; + actor.set_scale(0, 0); } - } -} + + let rect = actor.meta_window.get_buffer_rect(); + let [xDest, yDest] = [rect.x, rect.y]; + + actor.show(); + actor.ease({ + scale_x: 1, + scale_y: 1, + x: xDest, + y: yDest, + duration: MINIMIZE_WINDOW_ANIMATION_TIME, + mode: MINIMIZE_WINDOW_ANIMATION_MODE, + onStopped: () => this._unminimizeWindowDone(shellwm, actor), + }); + // } + }, +}; diff --git a/extensions/vertical-workspaces/windowPreview.js b/extensions/vertical-workspaces/lib/windowPreview.js index 013f48c..5d2bd61 100644 --- a/extensions/vertical-workspaces/windowPreview.js +++ b/extensions/vertical-workspaces/lib/windowPreview.js @@ -1,7 +1,7 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * windowPreview.js - * + * * @author GdH <G-dH@github.com> * @copyright 2022 - 2023 * @license GPL-3.0 @@ -10,7 +10,7 @@ 'use strict'; -const { Clutter, GLib, Graphene, Meta, Shell, St } = imports.gi; +const { Clutter, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi; const Main = imports.ui.main; const WindowPreview = imports.ui.windowPreview; @@ -18,40 +18,54 @@ const WindowPreview = imports.ui.windowPreview; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); -const _Util = Me.imports.util; +const _Util = Me.imports.lib.util; +const shellVersion = _Util.shellVersion; + let _overrides; const WINDOW_SCALE_TIME = imports.ui.windowPreview.WINDOW_SCALE_TIME; const WINDOW_ACTIVE_SIZE_INC = imports.ui.windowPreview.WINDOW_ACTIVE_SIZE_INC; const WINDOW_OVERLAY_FADE_TIME = imports.ui.windowPreview.WINDOW_OVERLAY_FADE_TIME; -const SEARCH_WINDOWS_PREFIX = Me.imports.windowSearchProvider.prefix; +const SEARCH_WINDOWS_PREFIX = Me.imports.lib.windowSearchProvider.prefix; const ControlsState = imports.ui.overviewControls.ControlsState; -var opt = null; +let opt; +let _firstRun = true; function update(reset = false) { - if (_overrides) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('windowPreviewModule', true); + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled + if (_firstRun && reset) + return; + + _firstRun = false; + + if (_overrides) _overrides.removeAll(); - } + if (reset) { _overrides = null; opt = null; + WindowPreview.WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 750; return; } - opt = Me.imports.settings.opt; _overrides = new _Util.Overrides(); _overrides.addOverride('WindowPreview', WindowPreview.WindowPreview.prototype, WindowPreviewCommon); - // move titles into window previews - _overrides.addInjection('WindowPreview', WindowPreview.WindowPreview.prototype, WindowPreviewInjections); + // A shorter timeout allows user to quickly cancel the selection by leaving the preview with the mouse pointer + if (opt.ALWAYS_ACTIVATE_SELECTED_WINDOW) + WindowPreview.WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 150; } -//----- WindowPreview ------------------------------------------------------------------ -var WindowPreviewInjections = { - _init: function() { +const WindowPreviewCommon = { + // injection to _init() + after__init() { const ICON_OVERLAP = 0.7; if (opt.WIN_PREVIEW_ICON_SIZE < 64) { @@ -91,7 +105,7 @@ var WindowPreviewInjections = { const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); const iconOverlap = opt.WIN_PREVIEW_ICON_SIZE * ICON_OVERLAP; // we cannot get proper title height before it gets to the stage, so 35 is estimated height + spacing - this._title.get_constraints()[1].offset = scaleFactor * (- iconOverlap - 35); + this._title.get_constraints()[1].offset = scaleFactor * (-iconOverlap - 35); this.set_child_above_sibling(this._title, null); // if window is created while the overview is shown, icon and title should be visible immediately if (Main.overview._overview._controls._stateAdjustment.value < 1) { @@ -100,34 +114,23 @@ var WindowPreviewInjections = { this._title.opacity = 0; } - if (opt.ALWAYS_SHOW_WIN_TITLES) { + if (opt.ALWAYS_SHOW_WIN_TITLES) this._title.show(); - this._stateConId = this._workspace._background._stateAdjustment.connect('notify::value', (adj) => { - this._title.opacity = Math.floor(adj.value) * 255; - }); - - } if (opt.OVERVIEW_MODE === 1) { // spread windows on hover this._wsStateConId = this.connect('enter-event', () => { // don't spread windows if user don't use pointer device at this moment - if (global.get_pointer()[0] === opt.showingPointerX) + if (global.get_pointer()[0] === opt.showingPointerX || Main.overview._overview._controls._stateAdjustment.value < 1) return; const adjustment = this._workspace._background._stateAdjustment; - if (!adjustment.value && !Main.overview._animationInProgress) { - opt.WORKSPACE_MODE = 1; - if (adjustment.value === 0) { - adjustment.value = 0; - adjustment.ease(1, { - duration: 200, - mode: Clutter.AnimationMode.EASE_OUT_QUAD - }); - } - } + opt.WORKSPACE_MODE = 1; + _Util.exposeWindows(adjustment, false); + this.disconnect(this._wsStateConId); }); } + if (opt.OVERVIEW_MODE) { // show window icon and title on ws windows spread this._stateAdjustmentSigId = this._workspace.stateAdjustment.connect('notify::value', this._updateIconScale.bind(this)); @@ -135,37 +138,52 @@ var WindowPreviewInjections = { // replace click action with custom one const action = this.get_actions()[0]; - this.remove_action(action); - const clickAction = new Clutter.ClickAction(); - clickAction.connect('clicked', (action) => { - const button = action.get_button(); + const handlerId = GObject.signal_handler_find(action, { signalId: 'clicked' }); + if (handlerId) + action.disconnect(handlerId); + + action.connect('clicked', act => { + const button = act.get_button(); if (button === Clutter.BUTTON_PRIMARY) { this._activate(); + return Clutter.EVENT_STOP; } else if (button === Clutter.BUTTON_SECONDARY) { // this action cancels long-press event and the 'long-press-cancel' event is used by the Shell to actually initiate DnD // so the dnd initiation needs to be removed if (this._longPressLater) { - Meta.later_remove(this._longPressLater); - delete this._longPressLater; + if (shellVersion >= 44) { + const laters = global.compositor.get_laters(); + laters.remove(this._longPressLater); + } else { + Meta.later_remove(this._longPressLater); + delete this._longPressLater; + } } const tracker = Shell.WindowTracker.get_default(); const appName = tracker.get_window_app(this.metaWindow).get_name(); _Util.activateSearchProvider(`${SEARCH_WINDOWS_PREFIX} ${appName}`); return Clutter.EVENT_STOP; } + return Clutter.EVENT_PROPAGATE; }); - clickAction.connect('long-press', this._onLongPress.bind(this)); - this.add_action(clickAction); - - this.connect('destroy', () => this._workspace.stateAdjustment.disconnect(this._stateConId)); - } -} + if (opt.WINDOW_ICON_CLICK_SEARCH) { + const iconClickAction = new Clutter.ClickAction(); + iconClickAction.connect('clicked', act => { + if (act.get_button() === Clutter.BUTTON_PRIMARY) { + const tracker = Shell.WindowTracker.get_default(); + const appName = tracker.get_window_app(this.metaWindow).get_name(); + _Util.activateSearchProvider(`${SEARCH_WINDOWS_PREFIX} ${appName}`); + return Clutter.EVENT_STOP; + } + return Clutter.EVENT_PROPAGATE; + }); + this._icon.add_action(iconClickAction); + } + }, -// WindowPreview -var WindowPreviewCommon = { - _updateIconScale: function() { + _updateIconScale() { let { currentState, initialState, finalState } = this._overviewAdjustment.getStateTransitionParams(); @@ -173,46 +191,60 @@ var WindowPreviewCommon = { const primaryMonitor = this.metaWindow.get_monitor() === global.display.get_primary_monitor(); const visible = - (initialState > ControlsState.HIDDEN || finalState > ControlsState.HIDDEN) - && !(finalState === ControlsState.APP_GRID && primaryMonitor); + (initialState > ControlsState.HIDDEN || finalState > ControlsState.HIDDEN) && + !(finalState === ControlsState.APP_GRID && primaryMonitor); - let scale = visible - ? (currentState >= 1 ? 1 : currentState % 1) : 0; - if (!primaryMonitor && + let scale = 0; + if (visible) + scale = currentState >= 1 ? 1 : currentState % 1; + + if (!primaryMonitor && opt.WORKSPACE_MODE && ((initialState === ControlsState.WINDOW_PICKER && finalState === ControlsState.APP_GRID) || (initialState === ControlsState.APP_GRID && finalState === ControlsState.WINDOW_PICKER)) - ) { + ) scale = 1; - /*} else if (primaryMonitor && ((initialState === ControlsState.WINDOW_PICKER && finalState === ControlsState.APP_GRID) || + else if (!primaryMonitor && opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE) + scale = 0; + /* } else if (primaryMonitor && ((initialState === ControlsState.WINDOW_PICKER && finalState === ControlsState.APP_GRID) || initialState === ControlsState.APP_GRID && finalState === ControlsState.HIDDEN)) {*/ - } else if (primaryMonitor && currentState > ControlsState.WINDOW_PICKER) { + else if (primaryMonitor && currentState > ControlsState.WINDOW_PICKER) scale = 0; - } - // in static workspace mode show icon and title on ws windows spread + + // in static workspace mode show icon and title on windows expose if (opt.OVERVIEW_MODE) { - const windowsSpread = this._workspace.stateAdjustment.value; - if (currentState === 1) { - scale = windowsSpread; - } else if (finalState === 1 || (finalState === 0 && !windowsSpread)) { + if (currentState === 1) + scale = opt.WORKSPACE_MODE; + else if (finalState === 1 || (finalState === 0 && !opt.WORKSPACE_MODE)) return; - } } - this._icon.set({ - scale_x: scale, - scale_y: scale, - }); + if (scale === 1) { + this._icon.ease({ + duration: 50, + scale_x: scale, + scale_y: scale, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + this._title.ease({ + duration: 100, + opacity: 255, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } else if (this._icon.scale_x !== 0) { + this._icon.set({ + scale_x: 0, + scale_y: 0, + }); + this._title.opacity = 0; + } // if titles are in 'always show' mode, we need to add transition between visible/invisible state // but the transition is quite expensive, // showing the titles at the end of the transition is good enough and workspace preview transition is much smoother - this._title.set({ - opacity: Math.floor(scale) * 255, - }); }, - showOverlay: function(animate) { + showOverlay(animate) { if (!this._overlayEnabled) return; @@ -220,7 +252,8 @@ var WindowPreviewCommon = { return; this._overlayShown = true; - //this._restack(); + if (!opt.ALWAYS_ACTIVATE_SELECTED_WINDOW) + this._restack(); // If we're supposed to animate and an animation in our direction // is already happening, let that one continue @@ -234,9 +267,9 @@ var WindowPreviewCommon = { ? [this._closeButton] : []; - if (!opt.ALWAYS_SHOW_WIN_TITLES) { + if (!opt.ALWAYS_SHOW_WIN_TITLES) toShow.push(this._title); - } + toShow.forEach(a => { a.opacity = 0; @@ -264,11 +297,17 @@ var WindowPreviewCommon = { this.emit('show-chrome'); }, - hideOverlay: function(animate) { + hideOverlay(animate) { if (!this._overlayShown) return; this._overlayShown = false; - //this._restack(); + if (opt.ALWAYS_ACTIVATE_SELECTED_WINDOW && Main.overview._overview.controls._stateAdjustment.value < 1) { + this.get_parent()?.set_child_above_sibling(this, null); + this._activateSelected = true; + } + + if (!opt.ALWAYS_ACTIVATE_SELECTED_WINDOW) + this._restack(); // If we're supposed to animate and an animation in our direction // is already happening, let that one continue @@ -280,9 +319,9 @@ var WindowPreviewCommon = { const toHide = [this._closeButton]; - if (!opt.ALWAYS_SHOW_WIN_TITLES) { + if (!opt.ALWAYS_SHOW_WIN_TITLES) toHide.push(this._title); - } + toHide.forEach(a => { a.opacity = 255; a.ease({ @@ -304,18 +343,24 @@ var WindowPreviewCommon = { }, _onDestroy() { - // fix for upstream bug - hideOverlay is called after windowPreview is destroyed, from the leave event callback - // but it still throws: - // clutter_actor_get_preferred_width: assertion 'CLUTTER_IS_ACTOR (self)' failed - // clutter_actor_get_preferred_height: assertion 'CLUTTER_IS_ACTOR (self)' failed - this.hideOverlay(false); + // workaround for upstream bug - hideOverlay is called after windowPreview is destroyed, from the leave event callback + // hiding the preview now avoids firing the post-mortem leave event + this.hide(); + if (this._activateSelected) + this._activate(); this.metaWindow._delegate = null; this._delegate = null; if (this._longPressLater) { - Meta.later_remove(this._longPressLater); - delete this._longPressLater; + if (shellVersion >= 44) { + const laters = global.compositor.get_laters(); + laters.remove(this._longPressLater); + delete this._longPressLater; + } else { + Meta.later_remove(this._longPressLater); + delete this._longPressLater; + } } if (this._idleHideOverlayId > 0) { @@ -328,8 +373,7 @@ var WindowPreviewCommon = { this.inDrag = false; } - if (this._stateAdjustmentSigId) { + if (this._stateAdjustmentSigId) this._workspace.stateAdjustment.disconnect(this._stateAdjustmentSigId); - } - } -} + }, +}; diff --git a/extensions/vertical-workspaces/windowSearchProvider.js b/extensions/vertical-workspaces/lib/windowSearchProvider.js index 3256742..5f90784 100644 --- a/extensions/vertical-workspaces/windowSearchProvider.js +++ b/extensions/vertical-workspaces/lib/windowSearchProvider.js @@ -1,5 +1,5 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * windowSearchProvider.js * * @author GdH <G-dH@github.com> @@ -9,13 +9,16 @@ 'use strict'; -const { GLib, GObject, Gio, Gtk, Meta, St, Shell } = imports.gi; +const { GLib, Gio, Meta, St, Shell } = imports.gi; const Main = imports.ui.main; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); -const Settings = Me.imports.settings; -const _ = Me.imports.settings._; +const Settings = Me.imports.lib.settings; +const _Util = Me.imports.lib.util; + +// gettext +const _ = Settings._; const shellVersion = Settings.shellVersion; @@ -36,18 +39,15 @@ const Action = { CLOSE: 1, CLOSE_ALL: 2, MOVE_TO_WS: 3, - MOVE_ALL_TO_WS: 4 -} - -function init() { -} + MOVE_ALL_TO_WS: 4, +}; function getOverviewSearchResult() { - return Main.overview._overview.controls._searchController._searchResults; + return Main.overview._overview.controls._searchController._searchResults; } function update(reset = false) { - opt = Me.imports.settings.opt; + opt = Me.imports.lib.settings.opt; if (!reset && opt.WINDOW_SEARCH_PROVIDER_ENABLED && !windowSearchProvider) { enable(); } else if (reset || !opt.WINDOW_SEARCH_PROVIDER_ENABLED) { @@ -62,7 +62,7 @@ function enable() { GLib.PRIORITY_DEFAULT, 2000, () => { - if (windowSearchProvider == null) { + if (!windowSearchProvider) { windowSearchProvider = new WindowSearchProvider(opt); getOverviewSearchResult()._registerProvider( windowSearchProvider @@ -87,58 +87,6 @@ function disable() { } } -function fuzzyMatch(term, text) { - let pos = -1; - const matches = []; - // convert all accented chars to their basic form and to lower case - const _text = text;//.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); - const _term = term.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); - - // if term matches the substring exactly, gains the highest weight - if (_text.includes(_term)) { - return 0; - } - - for (let i = 0; i < _term.length; i++) { - let c = _term[i]; - let p; - if (pos > 0) - p = _term[i - 1]; - while (true) { - pos += 1; - if (pos >= _text.length) { - return -1; - } - if (_text[pos] == c) { - matches.push(pos); - break; - } else if (_text[pos] == p) { - matches.pop(); - matches.push(pos); - } - } - } - - // add all position to get a weight of the result - // results closer to the beginning of the text and term characters closer to each other will gain more weight. - return matches.reduce((r, p) => r + p) - matches.length * matches[0] + matches[0]; -} - -function strictMatch(term, text) { - // remove diacritics and accents from letters - let s = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); - let p = term.toLowerCase(); - let ps = p.split(/ +/); - - // allows to use multiple exact patterns separated by a space in arbitrary order - for (let w of ps) { // escape regex control chars - if (!s.match(w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) { - return -1; - } - } - return 0; -} - function makeResult(window, i) { const app = Shell.WindowTracker.get_default().get_window_app(window); const appName = app ? app.get_name() : 'Unknown'; @@ -146,13 +94,13 @@ function makeResult(window, i) { const wsIndex = window.get_workspace().index(); return { - 'id': i, - // convert all accented chars to their basic form and lower case for search - 'name': `${wsIndex + 1}: ${windowTitle} ${appName}`.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(), - 'appName': appName, - 'windowTitle': windowTitle, - 'window': window - } + 'id': i, + // convert all accented chars to their basic form and lower case for search + 'name': `${wsIndex + 1}: ${windowTitle} ${appName}`.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(), + appName, + windowTitle, + window, + }; } const closeSelectedRegex = /^\/x!$/; @@ -160,29 +108,29 @@ const closeAllResultsRegex = /^\/xa!$/; const moveToWsRegex = /^\/m[0-9]+$/; const moveAllToWsRegex = /^\/ma[0-9]+$/; -var WindowSearchProvider = class WindowSearchProvider { - constructor(gOptions) { - this._gOptions = gOptions; - this.appInfo = Gio.AppInfo.create_from_commandline('true', 'Open Windows', null); - this.appInfo.get_description = () => 'List of open windows'; - this.appInfo.get_name = () => 'Open Windows'; - this.appInfo.get_id = () => `${Me.metadata.uuid} ${this.title}`; +const WindowSearchProvider = class WindowSearchProvider { + constructor() { + this.id = `open-windows@${Me.metadata.uuid}`; + this.appInfo = Gio.AppInfo.create_from_commandline('true', _('Open Windows'), null); + this.appInfo.get_description = () => _('List of open windows'); + this.appInfo.get_name = () => _('Open Windows'); + this.appInfo.get_id = () => this.id; this.appInfo.get_icon = () => Gio.icon_new_for_string('focus-windows-symbolic'); this.appInfo.should_show = () => true; - this.title = 'Window Search Provider', + this.canLaunchSearch = true; this.isRemoteProvider = false; this.action = 0; } - _getResultSet (terms) { + _getResultSet(terms) { // do not modify original terms let termsCopy = [...terms]; // search for terms without prefix termsCopy[0] = termsCopy[0].replace(prefix, ''); - /*if (gOptions.get('searchWindowsCommands')) { + /* if (gOptions.get('searchWindowsCommands')) { this.action = 0; this.targetWs = 0; @@ -208,46 +156,45 @@ var WindowSearchProvider = class WindowSearchProvider { const candidates = this.windows; const _terms = [].concat(termsCopy); - let match; + // let match; const term = _terms.join(' '); - match = (s) => { + /* match = s => { return fuzzyMatch(term, s); - } + }; */ const results = []; let m; for (let key in candidates) { - if (this._gOptions.get('searchFuzzy')) { - m = fuzzyMatch(term, candidates[key].name); - } else { - m = strictMatch(term, candidates[key].name); - } - if (m !== -1) { + if (opt.SEARCH_FUZZY) + m = _Util.fuzzyMatch(term, candidates[key].name); + else + m = _Util.strictMatch(term, candidates[key].name); + + if (m !== -1) results.push({ weight: m, id: key }); - } } results.sort((a, b) => a.weight > b.weight); const currentWs = global.workspace_manager.get_active_workspace_index(); // prefer current workspace results.sort((a, b) => (this.windows[a.id].window.get_workspace().index() !== currentWs) && (this.windows[b.id].window.get_workspace().index() === currentWs)); - results.sort((a, b) => ((_terms != ' ') && (a.weight > 0 && b.weight === 0))); + results.sort((a, b) => (_terms !== ' ') && (a.weight > 0 && b.weight === 0)); - this.resultIds = results.map((item) => item.id); + this.resultIds = results.map(item => item.id); return this.resultIds; } - getResultMetas (resultIds, callback = null) { - const metas = resultIds.map((id) => this.getResultMeta(id)); - if (shellVersion >= 43) { + getResultMetas(resultIds, callback = null) { + const metas = resultIds.map(id => this.getResultMeta(id)); + if (shellVersion >= 43) return new Promise(resolve => resolve(metas)); - } else { + else callback(metas); - } + return null; } - getResultMeta (resultId) { + getResultMeta(resultId) { const result = this.windows[resultId]; const wsIndex = result.window.get_workspace().index(); const app = Shell.WindowTracker.get_default().get_window_app(result.window); @@ -255,32 +202,30 @@ var WindowSearchProvider = class WindowSearchProvider { 'id': resultId, 'name': `${wsIndex + 1}: ${result.windowTitle}`, 'description': result.appName, - 'createIcon': (size) => { + 'createIcon': size => { return app ? app.create_icon_texture(size) : new St.Icon({ icon_name: 'icon-missing', icon_size: size }); - } - } + }, + }; } - launchSearch(terms, timeStamp) { + launchSearch(/* terms, timeStamp*/) { } - activateResult (resultId, terms, timeStamp) { - const [,,state] = global.get_pointer(); - - const isCtrlPressed = (state & ModifierType.CONTROL_MASK) != 0; - const isShiftPressed = (state & ModifierType.SHIFT_MASK) != 0; + activateResult(resultId/* , terms, timeStamp*/) { + const isCtrlPressed = _Util.isCtrlPressed(); + const isShiftPressed = _Util.isShiftPressed(); this.action = 0; this.targetWs = 0; this.targetWs = global.workspaceManager.get_active_workspace().index() + 1; - if (isShiftPressed && !isCtrlPressed) { + if (isShiftPressed && !isCtrlPressed) this.action = Action.MOVE_TO_WS; - } else if (isShiftPressed && isCtrlPressed) { + else if (isShiftPressed && isCtrlPressed) this.action = Action.MOVE_ALL_TO_WS; - } + if (!this.action) { const result = this.windows[resultId]; @@ -306,53 +251,55 @@ var WindowSearchProvider = class WindowSearchProvider { _closeWindows(ids) { let time = global.get_current_time(); - for (let i = 0; i < ids.length; i++) { + for (let i = 0; i < ids.length; i++) this.windows[ids[i]].window.delete(time + i); - } + Main.notify('Window Search Provider', `Closed ${ids.length} windows.`); } _moveWindowsToWs(selectedId, resultIds) { const workspace = global.workspaceManager.get_active_workspace(); - for (let i = 0; i < resultIds.length; i++) { + for (let i = 0; i < resultIds.length; i++) this.windows[resultIds[i]].window.change_workspace(workspace); - } + const selectedWin = this.windows[selectedId].window; selectedWin.activate_with_workspace(global.get_current_time(), workspace); } - getInitialResultSet (terms, callback, cancellable = null) { - if (shellVersion >=43) { - cancellable = callback; - } + getInitialResultSet(terms, callback/* , cancellable = null*/) { + // In GS 43 callback arg has been removed + /* if (shellVersion >= 43) + cancellable = callback;*/ + let windows; this.windows = windows = {}; global.display.get_tab_list(Meta.TabList.NORMAL, null).filter(w => w.get_workspace() !== null).map( - (v, i) => windows[`${i}-${v.get_id()}`] = makeResult(v, `${i}-${v.get_id()}`) + (v, i) => { + windows[`${i}-${v.get_id()}`] = makeResult(v, `${i}-${v.get_id()}`); + return windows[`${i}-${v.get_id()}`]; + } ); - - - if (shellVersion >= 43) { + if (shellVersion >= 43) return new Promise(resolve => resolve(this._getResultSet(terms))); - } else { + else callback(this._getResultSet(terms)); - } + return null; } - filterResults (results, maxResults) { - //return results.slice(0, maxResults); + filterResults(results /* , maxResults*/) { + // return results.slice(0, maxResults); return results; } - getSubsearchResultSet (previousResults, terms, callback, cancellable) { + getSubsearchResultSet(previousResults, terms, callback/* , cancellable*/) { // if we return previous results, quick typers get non-actual results callback(this._getResultSet(terms)); } - createResultOjbect(resultMeta) { + /* createResultObject(resultMeta) { const app = Shell.WindowTracker.get_default().get_window_app(resultMeta.id); return new AppIcon(app); - } -} + }*/ +}; diff --git a/extensions/vertical-workspaces/workspace.js b/extensions/vertical-workspaces/lib/workspace.js index 84ca696..3b61a6d 100644 --- a/extensions/vertical-workspaces/workspace.js +++ b/extensions/vertical-workspaces/lib/workspace.js @@ -1,7 +1,7 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * workspace.js - * + * * @author GdH <G-dH@github.com> * @copyright 2022 - 2023 * @license GPL-3.0 @@ -10,7 +10,7 @@ 'use strict'; -const { Clutter, St, Graphene } = imports.gi; +const { St, Graphene } = imports.gi; const Main = imports.ui.main; const Util = imports.misc.util; @@ -19,33 +19,37 @@ const Workspace = imports.ui.workspace; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); -const _Util = Me.imports.util; -let _overrides; +const _Util = Me.imports.lib.util; +let _overrides; let opt; +let _firstRun = true; -const BACKGROUND_CORNER_RADIUS_PIXELS = 40; +function update(reset = false) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('workspaceModule', true); + reset = reset || !moduleEnabled; + // don't even touch this module if disabled + if (_firstRun && reset) + return; -function update(reset = false) { - if (_overrides) { + _firstRun = false; + + if (_overrides) _overrides.removeAll(); - } + if (reset) { - imports.ui.workspace.WINDOW_PREVIEW_MAXIMUM_SCALE = 0.95; + Workspace.WINDOW_PREVIEW_MAXIMUM_SCALE = 0.95; _overrides = null; opt = null; return; } - opt = Me.imports.settings.opt; - _overrides = new _Util.Overrides(); _overrides.addOverride('WorkspaceBackground', Workspace.WorkspaceBackground.prototype, WorkspaceBackground); - // fix window scaling in workspace state 0 - _overrides.addInjection('WorkspaceLayout', Workspace.WorkspaceLayout.prototype, WorkspaceLayoutInjections); // fix overlay base for Vertical Workspaces _overrides.addOverride('WorkspaceLayout', Workspace.WorkspaceLayout.prototype, WorkspaceLayout); @@ -54,32 +58,35 @@ function update(reset = false) { // workaround for upstream bug (that is not that invisible in default shell) // smaller window cannot be scaled below 0.95 (WINDOW_PREVIEW_MAXIMUM_SCALE) -// when its target scale for spread windows view (workspace state 1) is bigger than the scale needed for ws state 0. +// when its target scale for exposed windows view (workspace state 1) is bigger than the scale needed for ws state 0. // in workspace state 0 where windows are not spread and window scale should follow workspace scale, // this window follows proper top left corner position, but doesn't scale with the workspace // so it looks bad and the window can exceed border of the workspace // extremely annoying in OVERVIEW_MODE 1 with single smaller window on the workspace, also affects appGrid transition animation -var WorkspaceLayoutInjections = { - _init: function() { - this._stateAdjustment.connect('notify::value', () => { - if (opt.OVERVIEW_MODE !== 1) return; - // scale 0.1 for window state 0 just needs to be smaller then possible scale of any window in spread view - const scale = this._stateAdjustment.value ? 0.95 : 0.1; - if (scale !== Workspace.WINDOW_PREVIEW_MAXIMUM_SCALE || this._stateAdjustment.value === 1) { - // when transition to ws state 1 begins, replace the constant with the original one - // disadvantage - the value changes for all workspaces, so one affects others - // that can be visible in certain situations but not a big deal. - Workspace.WINDOW_PREVIEW_MAXIMUM_SCALE = scale; - // and force recalculation of the target layout, so the transition will be smooth - this._needsLayout = true; - } - }); - } -} -var WorkspaceLayout = { +// disadvantage of following workaround - the WINDOW_PREVIEW_MAXIMUM_SCALE value is common for every workspace, +// on multi-monitor system can be visible unwanted scaling of windows on workspace in WORKSPACE_MODE 0 (windows not spread) +// when leaving overview while any other workspace is in the WORKSPACE_MODE 1. +const WorkspaceLayout = { + // injection to _init() + after__init() { + if (opt.OVERVIEW_MODE === 1) { + this._stateAdjustment.connect('notify::value', () => { + // scale 0.1 for window state 0 just needs to be smaller then possible scale of any window in spread view + const scale = this._stateAdjustment.value ? 0.95 : 0.1; + if (scale !== this.WINDOW_PREVIEW_MAXIMUM_SCALE) { + this.WINDOW_PREVIEW_MAXIMUM_SCALE = scale; + // when transition to ws state 1 (WINDOW_PICKER) begins, replace the constant with the original one + Workspace.WINDOW_PREVIEW_MAXIMUM_SCALE = scale; + // and force recalculation of the target layout, so the transition will be smooth + this._needsLayout = true; + } + }); + } + }, + // this fixes wrong size and position calculation of window clones while moving overview to the next (+1) workspace if vertical ws orientation is enabled in GS - _adjustSpacingAndPadding: function(rowSpacing, colSpacing, containerBox) { + _adjustSpacingAndPadding(rowSpacing, colSpacing, containerBox) { if (this._sortedWindows.length === 0) return [rowSpacing, colSpacing, containerBox]; @@ -103,11 +110,11 @@ var WorkspaceLayout = { const monitor = Main.layoutManager.monitors[this._monitorIndex]; const bottomPoint = new Graphene.Point3D(); - if (vertical) { + if (vertical) bottomPoint.x = containerBox.x2; - } else { + else bottomPoint.y = containerBox.y2; - } + const transformedBottomPoint = this._container.apply_transform_to_point(bottomPoint); @@ -117,30 +124,29 @@ var WorkspaceLayout = { const [, bottomOverlap] = window.overlapHeights(); - if ((bottomOverlap + oversize) > bottomFreeSpace && !vertical) { + if ((bottomOverlap + oversize) > bottomFreeSpace && !vertical) containerBox.y2 -= (bottomOverlap + oversize) - bottomFreeSpace; - } } return [rowSpacing, colSpacing, containerBox]; - } -} + }, +}; -var WorkspaceBackground = { - _updateBorderRadius: function(value = false) { +const WorkspaceBackground = { + _updateBorderRadius(value = false) { // don't round already rounded corners during exposing windows - if (value === false && opt.OVERVIEW_MODE === 1) { + if (value === false && opt.OVERVIEW_MODE === 1) return; - } + const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); - const cornerRadius = scaleFactor * BACKGROUND_CORNER_RADIUS_PIXELS; + const cornerRadius = scaleFactor * opt.WS_PREVIEW_BG_RADIUS; const backgroundContent = this._bgManager.backgroundActor.content; - value = (value !==false) - ? value - : this._stateAdjustment.value; + value = value !== false + ? value + : this._stateAdjustment.value; backgroundContent.rounded_clip_radius = Util.lerp(0, cornerRadius, value); - } -} + }, +}; diff --git a/extensions/vertical-workspaces/workspaceAnimation.js b/extensions/vertical-workspaces/lib/workspaceAnimation.js index 40f61b8..07008c6 100644 --- a/extensions/vertical-workspaces/workspaceAnimation.js +++ b/extensions/vertical-workspaces/lib/workspaceAnimation.js @@ -1,7 +1,7 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * workspacesAnimation.js - * + * * @author GdH <G-dH@github.com> * @copyright 2022 - 2023 * @license GPL-3.0 @@ -9,31 +9,40 @@ */ 'use strict'; - -const { St } = imports.gi; - const Main = imports.ui.main; const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup; const WorkspaceAnimation = imports.ui.workspaceAnimation; const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.util; +const _Util = Me.imports.lib.util; -// touching module properties defined by const/let for the first time returns undefined in GS 42, so we touch it here before we use it -WorkspaceAnimation.MonitorGroup; +// first reference to constant defined using const in other module returns undefined, the MonitorGroup const will remain empty and unused +let MonitorGroupDummy = WorkspaceAnimation.MonitorGroup; +MonitorGroupDummy = null; let _origBaseDistance; let _wsAnimationSwipeBeginId; +let _wsAnimationSwipeUpdateId; let _wsAnimationSwipeEndId; + let _overrides; let opt; +let _firstRun = true; function update(reset = false) { - if (_overrides) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('workspaceAnimationModule', true); + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled + if (_firstRun && reset) + return; + + _firstRun = false; + + if (_overrides) _overrides.removeAll(); - } - opt = Me.imports.settings.opt; - if (reset || !opt.STATIC_WS_SWITCHER_BG) { + if (reset || !moduleEnabled) { _connectWsAnimationSwipeTracker(true); _overrideMonitorGroupProperty(true); _overrides = null; @@ -41,17 +50,19 @@ function update(reset = false) { return; } - _overrides = new _Util.Overrides(); - _connectWsAnimationSwipeTracker(); - _overrideMonitorGroupProperty(); + if (opt.STATIC_WS_SWITCHER_BG) { + _overrides = new _Util.Overrides(); + _overrideMonitorGroupProperty(); + _overrides.addOverride('WorkspaceAnimationMonitorGroup', WorkspaceAnimation.MonitorGroup.prototype, MonitorGroup); + } - _overrides.addInjection('WorkspaceAnimationMonitorGroup', WorkspaceAnimation.MonitorGroup.prototype, MonitorGroupInjections); + _connectWsAnimationSwipeTracker(); } // remove spacing between workspaces during transition to remove flashing wallpaper between workspaces with maximized windows function _overrideMonitorGroupProperty(reset = false) { if (!_origBaseDistance) - _origBaseDistance = Object.getOwnPropertyDescriptor(WorkspaceAnimation.MonitorGroup.prototype,'baseDistance').get; + _origBaseDistance = Object.getOwnPropertyDescriptor(WorkspaceAnimation.MonitorGroup.prototype, 'baseDistance').get; let getter; if (reset) { @@ -59,28 +70,29 @@ function _overrideMonitorGroupProperty(reset = false) { getter = { get: _origBaseDistance }; } else { getter = { - get: function () { - //const spacing = 100 * St.ThemeContext.get_for_stage(global.stage).scale_factor; + get() { + // const spacing = 100 * imports.gi.St.ThemeContext.get_for_stage(global.stage).scale_factor; const spacing = 0; if (global.workspace_manager.layout_rows === -1) return this._monitor.height + spacing + (opt.PANEL_MODE ? Main.panel.height : 0); // compensation for hidden panel else return this._monitor.width + spacing; - } - } + }, + }; } if (getter) - Object.defineProperty(WorkspaceAnimation.MonitorGroup.prototype, "baseDistance", getter); + Object.defineProperty(WorkspaceAnimation.MonitorGroup.prototype, 'baseDistance', getter); } -const MonitorGroupInjections = { - _init: function() { +const MonitorGroup = { + // injection to _init() + after__init() { // we have two options to implement static bg feature // one is adding background to monitorGroup // but this one has disadvantage - sticky windows will be always on top of animated windows // which is bad for conky, for example, that window should be always below - /*this._bgManager = new Background.BackgroundManager({ + /* this._bgManager = new Background.BackgroundManager({ container: this, monitorIndex: this._monitor.index, controlPosition: false, @@ -92,21 +104,22 @@ const MonitorGroupInjections = { this.set_style('background-color: transparent;'); // stickyGroup holds the Always on Visible Workspace windows to keep them static and above other windows during animation const stickyGroup = this.get_children()[1]; - stickyGroup._windowRecords.forEach((r, index) => { + stickyGroup._windowRecords.forEach(r => { const metaWin = r.windowActor.metaWindow; // conky is sticky but should never get above other windows during ws animation // so we hide it from the overlay group, we will see the original if not covered by other windows - if (metaWin.wm_class == 'conky') { + if (metaWin.wm_class === 'conky') r.clone.opacity = 0; - } - }) + }); this._hiddenWindows = []; // remove (hide) background wallpaper from the animation, we will see the original one - this._workspaceGroups.forEach(w => w._background.opacity = 0); + this._workspaceGroups.forEach(w => { + w._background.opacity = 0; + }); // hide (scale to 0) all non-sticky windows, their clones will be animated global.get_window_actors().forEach(actor => { const metaWin = actor.metaWindow; - if (metaWin?.get_monitor() === this._monitor.index && !(metaWin?.wm_class == 'conky' && metaWin?.is_on_all_workspaces())) { //* && !w.is_on_all_workspaces()*/) { + if (metaWin?.get_monitor() === this._monitor.index && !(metaWin?.wm_class === 'conky' && metaWin?.is_on_all_workspaces())) { //* && !w.is_on_all_workspaces()*/) { // hide original window. we cannot use opacity since it also affects clones. // scaling them to 0 works well actor.scale_x = 0; @@ -116,16 +129,13 @@ const MonitorGroupInjections = { // restore all hidden windows at the end of animation // todo - actors removed during transition need to be removed from the list to avoid access to destroyed actor - this.connect('destroy', () =>{ + this.connect('destroy', () => { this._hiddenWindows.forEach(actor => { actor.scale_x = 1; }); }); - } -} - - -// ------ connect Ws Animation Swipe Tracker -------------- + }, +}; function _connectWsAnimationSwipeTracker(reset = false) { if (reset) { @@ -139,35 +149,30 @@ function _connectWsAnimationSwipeTracker(reset = false) { } } else if (!_wsAnimationSwipeBeginId) { // display ws switcher popup when gesture begins and connect progress - _wsAnimationSwipeBeginId = Main.wm._workspaceAnimation._swipeTracker.connect('begin', _connectWsAnimationProgress); + _wsAnimationSwipeBeginId = Main.wm._workspaceAnimation._swipeTracker.connect('begin', () => _connectWsAnimationProgress(true)); // we want to be sure that popup with the final ws index show up when gesture ends - _wsAnimationSwipeEndId = Main.wm._workspaceAnimation._swipeTracker.connect('end', () => _showWsSwitcherPopup(0)); + _wsAnimationSwipeEndId = Main.wm._workspaceAnimation._swipeTracker.connect('end', (tracker, duration, endProgress) => _connectWsAnimationProgress(false, endProgress)); } } -function _connectWsAnimationProgress() { - if (Main.overview.visible) return; - - // progress is being updated only when user finished gesture and the animation continues on "autopilot" - Main.wm._workspaceAnimation._switchData.monitors[0].connect('notify::progress',(actor) => { - const progress = actor.progress % 1; - let direction = 0; - if (!actor._prg) actor._prg = progress; - else if (!progress) return; - else if (progress < actor._prg) direction = -1; - else if (progress > actor._prg) direction = 1; - if (progress < 0.6 && progress > 0.4) - _showWsSwitcherPopup(direction); - }); - - // display popup when gesture begins - _showWsSwitcherPopup(0); +function _connectWsAnimationProgress(connect, endProgress = null) { + if (Main.overview.visible) + return; + + if (connect && !_wsAnimationSwipeUpdateId) { + _wsAnimationSwipeUpdateId = Main.wm._workspaceAnimation._swipeTracker.connect('update', (tracker, progress) => _showWsSwitcherPopup(progress)); + } else if (!connect && _wsAnimationSwipeUpdateId) { + Main.wm._workspaceAnimation._swipeTracker.disconnect(_wsAnimationSwipeUpdateId); + _wsAnimationSwipeUpdateId = 0; + _showWsSwitcherPopup(Math.round(endProgress)); + } } -function _showWsSwitcherPopup(direction) { - if (Main.overview.visible) return; +function _showWsSwitcherPopup(progress) { + if (Main.overview.visible) + return; - const wsIndex = global.workspaceManager.get_active_workspace_index() + direction; + const wsIndex = Math.round(progress); if (Main.wm._workspaceSwitcherPopup === null) { Main.wm._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup(); Main.wm._workspaceSwitcherPopup.connect('destroy', () => { diff --git a/extensions/vertical-workspaces/lib/workspaceSwitcherPopup.js b/extensions/vertical-workspaces/lib/workspaceSwitcherPopup.js new file mode 100644 index 0000000..972f35e --- /dev/null +++ b/extensions/vertical-workspaces/lib/workspaceSwitcherPopup.js @@ -0,0 +1,90 @@ +/** + * V-Shell (Vertical Workspaces) + * workspacesSwitcherPopup.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; + +const Main = imports.ui.main; +const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup; + +const ExtensionUtils = imports.misc.extensionUtils; +const Me = ExtensionUtils.getCurrentExtension(); + +const _Util = Me.imports.lib.util; +let _overrides; + +let opt; +let _firstRun = true; + +function update(reset = false) { + opt = Me.imports.lib.settings.opt; + const moduleEnabled = opt.get('workspaceSwitcherPopupModule', true); + reset = reset || !moduleEnabled; + + // don't even touch this module if disabled + if (_firstRun && reset) + return; + + _firstRun = false; + + if (_overrides) + _overrides.removeAll(); + + if (reset) { + _overrides = null; + opt = null; + return; + } + + _overrides = new _Util.Overrides(); + + const enabled = global.settings.get_strv('enabled-extensions'); + const allowWsPopupInjection = !(enabled.includes('workspace-switcher-manager@G-dH.github.com') || enabled.includes('WsSwitcherPopupManager@G-dH.github.com-dev')); + if (allowWsPopupInjection) { // 1-VERTICAL, 0-HORIZONTAL + _overrides.addOverride('WorkspaceSwitcherPopup', WorkspaceSwitcherPopup.WorkspaceSwitcherPopup.prototype, WorkspaceSwitcherPopupOverride); + } +} + +const WorkspaceSwitcherPopupOverride = { + // injection to _init() + after__init() { + if (opt.ORIENTATION) { // 1-VERTICAL, 0-HORIZONTAL + this._list.vertical = true; + } + this._list.set_style('margin: 0;'); + this.remove_constraint(this.get_constraints()[0]); + }, + + // injection to display() + after_display() { + if (opt.WS_SW_POPUP_MODE) + this._setPopupPosition(); + else + this.opacity = 0; + }, + + _setPopupPosition() { + let workArea; + if (opt.WS_SW_POPUP_MODE === 1) { + // workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);*/ + workArea = global.display.get_monitor_geometry(Main.layoutManager.primaryIndex); + } else { + // workArea = Main.layoutManager.getWorkAreaForMonitor(global.display.get_current_monitor()); + workArea = global.display.get_monitor_geometry(global.display.get_current_monitor()); + } + + let [, natHeight] = this.get_preferred_height(global.screen_width); + let [, natWidth] = this.get_preferred_width(natHeight); + let h = opt.WS_SW_POPUP_H_POSITION; + let v = opt.WS_SW_POPUP_V_POSITION; + this.x = workArea.x + Math.floor((workArea.width - natWidth) * h); + this.y = workArea.y + Math.floor((workArea.height - natHeight) * v); + this.set_position(this.x, this.y); + }, +}; diff --git a/extensions/vertical-workspaces/workspaceThumbnail.js b/extensions/vertical-workspaces/lib/workspaceThumbnail.js index bbf8900..d0bc206 100644 --- a/extensions/vertical-workspaces/workspaceThumbnail.js +++ b/extensions/vertical-workspaces/lib/workspaceThumbnail.js @@ -1,7 +1,7 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * workspaceThumbnail.js - * + * * @author GdH <G-dH@github.com> * @copyright 2022 - 2023 * @license GPL-3.0 @@ -10,7 +10,7 @@ 'use strict'; -const { Clutter, Graphene, Meta, Shell, St } = imports.gi; +const { GLib, Clutter, Graphene, Meta, Shell, St } = imports.gi; const DND = imports.ui.dnd; const Main = imports.ui.main; const Background = imports.ui.background; @@ -22,70 +22,65 @@ const ControlsState = imports.ui.overviewControls.ControlsState; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); -const _Util = Me.imports.util; +// gettext +const _ = Me.imports.lib.settings._; + +const _Util = Me.imports.lib.util; +const shellVersion = _Util.shellVersion; + let _overrides; const WORKSPACE_CUT_SIZE = 10; -const original_MAX_THUMBNAIL_SCALE = WorkspaceThumbnail.MAX_THUMBNAIL_SCALE; -let MAX_THUMBNAIL_SCALE; - -var opt = null; +const _originalMaxThumbnailScale = WorkspaceThumbnail.MAX_THUMBNAIL_SCALE; +let opt = null; function update(reset = false) { - if (_overrides) { + if (_overrides) _overrides.removeAll(); - } + if (reset) { - if (original_MAX_THUMBNAIL_SCALE) - WorkspaceThumbnail.MAX_THUMBNAIL_SCALE = original_MAX_THUMBNAIL_SCALE; + if (_originalMaxThumbnailScale) + WorkspaceThumbnail.MAX_THUMBNAIL_SCALE = _originalMaxThumbnailScale; _overrides = null; opt = null; return; } - opt = Me.imports.settings.opt; - - MAX_THUMBNAIL_SCALE = opt.MAX_THUMBNAIL_SCALE; - WorkspaceThumbnail.MAX_THUMBNAIL_SCALE = MAX_THUMBNAIL_SCALE; - + opt = Me.imports.lib.settings.opt; _overrides = new _Util.Overrides(); + // don't limit max thumbnail scale for other clients than overview, for example AATWS. + WorkspaceThumbnail.MAX_THUMBNAIL_SCALE = 1; + _overrides.addOverride('WorkspaceThumbnail', WorkspaceThumbnail.WorkspaceThumbnail.prototype, WorkspaceThumbnailCommon); + _overrides.addOverride('ThumbnailsBoxCommon', WorkspaceThumbnail.ThumbnailsBox.prototype, ThumbnailsBoxCommon); - if (opt.ORIENTATION === Clutter.Orientation.VERTICAL) { - _overrides.addOverride('ThumbnailsBox', WorkspaceThumbnail.ThumbnailsBox.prototype, ThumbnailsBoxVertical); - } else { - _overrides.addOverride('ThumbnailsBox', WorkspaceThumbnail.ThumbnailsBox.prototype, ThumbnailsBoxHorizontal); - } + // replacing opt.ORIENTATION local constant with boxOrientation internal variable allows external customers such as the AATWS extension to control the box orientation. + Main.overview._overview.controls._thumbnailsBox._boxOrientation = opt.ORIENTATION; } - -var WorkspaceThumbnailCommon = { - after__init: function () { - - //radius of ws thumbnail background - this.add_style_class_name('ws-tmb'); +const WorkspaceThumbnailCommon = { + // injection to _init() + after__init() { + // layout manager allows aligning widget children + this.layout_manager = new Clutter.BinLayout(); + // adding layout manager to tmb widget breaks wallpaper background aligning and rounded corners + // unless border is removed + if (opt.SHOW_WS_TMB_BG) + this.add_style_class_name('ws-tmb-labeled'); // add workspace thumbnails labels if enabled if (opt.SHOW_WST_LABELS) { // 0 - disable - // layout manager allows aligning widget children - this.layout_manager = new Clutter.BinLayout(); - // adding layout manager to tmb widget breaks wallpaper background aligning and rounded corners - // unless border is removed - if (opt.SHOW_WS_TMB_BG) - this.add_style_class_name('ws-tmb-labeled'); - - const getLabel = function() { + const getLabel = function () { const wsIndex = this.metaWorkspace.index(); let label = `${wsIndex + 1}`; if (opt.SHOW_WST_LABELS === 2) { // 2 - index + workspace name const settings = ExtensionUtils.getSettings('org.gnome.desktop.wm.preferences'); const wsLabels = settings.get_strv('workspace-names'); - if (wsLabels.length > wsIndex && wsLabels[wsIndex]) { + if (wsLabels.length > wsIndex && wsLabels[wsIndex]) label += `: ${wsLabels[wsIndex]}`; - } } else if (opt.SHOW_WST_LABELS === 3) { // 3- index + app name // global.display.get_tab_list offers workspace filtering using the second argument, but... // ... it sometimes includes windows from other workspaces, like minimized VBox machines, after Shell restarts @@ -93,16 +88,16 @@ var WorkspaceThumbnailCommon = { w => w.get_monitor() === this.monitorIndex && w.get_workspace().index() === wsIndex)[0]; if (metaWin) { - let tracker = Shell.WindowTracker.get_default(); - label += `: ${tracker.get_window_app(metaWin).get_name()}`; + const tracker = Shell.WindowTracker.get_default(); + const app = tracker.get_window_app(metaWin); + label += `: ${app ? app.get_name() : ''}`; } } else if (opt.SHOW_WST_LABELS === 4) { const metaWin = global.display.get_tab_list(0, null).filter( w => w.get_monitor() === this.monitorIndex && w.get_workspace().index() === wsIndex)[0]; - if (metaWin) { + if (metaWin) label += `: ${metaWin.title}`; - } } return label; }.bind(this); @@ -124,28 +119,96 @@ var WorkspaceThumbnailCommon = { this.add_child(this._wsLabel); this.set_child_above_sibling(this._wsLabel, null); - this._wsIndexSigId = this.metaWorkspace.connect('notify::workspace-index', () => { + this._wsIndexConId = this.metaWorkspace.connect('notify::workspace-index', () => { const newLabel = getLabel(); this._wsLabel.text = newLabel; + // avoid possibility of accessing non existing ws + if (this._updateLabelTimeout) { + GLib.source_remove(this._updateLabelTimeout); + this._updateLabelTimeout = 0; + } + }); + this._nWindowsConId = this.metaWorkspace.connect('notify::n-windows', () => { + // wait for new information + this._updateLabelTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, () => { + const newLabel = getLabel(); + this._wsLabel.text = newLabel; + this._updateLabelTimeout = 0; + return GLib.SOURCE_REMOVE; + }); }); + } + + if (opt.CLOSE_WS_BUTTON_MODE) { + const closeButton = new St.Icon({ + style_class: 'workspace-close-button', + icon_name: 'window-close-symbolic', + x_align: Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.START, + x_expand: true, + y_expand: true, + reactive: true, + opacity: 0, + }); + + closeButton.connect('button-release-event', () => { + if (opt.CLOSE_WS_BUTTON_MODE) { + this._closeWorkspace(); + return Clutter.EVENT_STOP; + } else { + return Clutter.EVENT_PROPAGATE; + } + }); + + closeButton.connect('button-press-event', () => { + return Clutter.EVENT_STOP; + }); + + closeButton.connect('enter-event', () => { + closeButton.opacity = 255; + if (!Meta.prefs_get_dynamic_workspaces() || (Meta.prefs_get_dynamic_workspaces() && global.workspace_manager.get_n_workspaces() - 1 !== this.metaWorkspace.index())) { + // color the button red if ready to react on clicks + if (opt.CLOSE_WS_BUTTON_MODE < 3 || (opt.CLOSE_WS_BUTTON_MODE === 3 && _Util.isCtrlPressed())) + closeButton.add_style_class_name('workspace-close-button-hover'); + } + }); + + closeButton.connect('leave-event', () => { + closeButton.remove_style_class_name('workspace-close-button-hover'); + }); + + this.add_child(closeButton); + this._closeButton = closeButton; + + this.reactive = true; + this._lastCloseClickTime = 0; + } - this.connect('destroy', () => this.metaWorkspace.disconnect(this._wsIndexSigId)); + if (opt.SHOW_WST_LABELS_ON_HOVER) + this._wsLabel.opacity = 0; + this.connect('enter-event', () => { + if (opt.CLOSE_WS_BUTTON_MODE && (!Meta.prefs_get_dynamic_workspaces() || (Meta.prefs_get_dynamic_workspaces() && global.workspace_manager.get_n_workspaces() - 1 !== this.metaWorkspace.index()))) + this._closeButton.opacity = 200; if (opt.SHOW_WST_LABELS_ON_HOVER) { - this._wsLabel.opacity = 0; - this.reactive = true; - this.connect('enter-event', ()=> this._wsLabel.ease({ + this._wsLabel.ease({ duration: 100, mode: Clutter.AnimationMode.EASE_OUT_QUAD, - opacity: this._wsLabel._maxOpacity - })); - this.connect('leave-event', ()=> this._wsLabel.ease({ + opacity: this._wsLabel._maxOpacity, + }); + } + }); + + this.connect('leave-event', () => { + this._closeButton.opacity = 0; + if (opt.SHOW_WST_LABELS_ON_HOVER) { + this._wsLabel.ease({ duration: 100, mode: Clutter.AnimationMode.EASE_OUT_QUAD, - opacity: 0 - })); + opacity: 0, + }); } - } + }); if (opt.SHOW_WS_TMB_BG) { this._bgManager = new Background.BackgroundManager({ @@ -157,32 +220,58 @@ var WorkspaceThumbnailCommon = { this._viewport.set_child_below_sibling(this._bgManager.backgroundActor, null); - this.connect('destroy', function () { + this.connect('destroy', () => { if (this._bgManager) this._bgManager.destroy(); this._bgManager = null; - }.bind(this)); + }); + // full brightness of the thumbnail bg draws unnecessary attention + // there is a grey bg under the wallpaper this._bgManager.backgroundActor.opacity = 220; + } - // this all is just for the small border radius... - /*const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); - const cornerRadius = scaleFactor * BACKGROUND_CORNER_RADIUS_PIXELS; - const backgroundContent = this._bgManager.backgroundActor.content; - backgroundContent.rounded_clip_radius = cornerRadius; + this.connect('destroy', () => { + if (this._wsIndexConId) + this.metaWorkspace.disconnect(this._wsIndexConId); + + if (this._nWindowsConId) + this.metaWorkspace.disconnect(this._nWindowsConId); + + if (this._updateLabelTimeout) + GLib.source_remove(this._updateLabelTimeout); + + if (this._bgManager) + this._bgManager.destroy(); + }); + }, + + _closeWorkspace() { + // CLOSE_WS_BUTTON_MODE 1: single click, 2: double-click, 3: Ctrl + + if (opt.CLOSE_WS_BUTTON_MODE === 2) { + const doubleClickTime = Clutter.Settings.get_default().double_click_time; + const clickDelay = Date.now() - this._lastCloseClickTime; + if (clickDelay > doubleClickTime) { + this._lastCloseClickTime = Date.now(); + return; + } + } else if (opt.CLOSE_WS_BUTTON_MODE === 3 && !_Util.isCtrlPressed()) { + return; + } - // the original clip has some addition at the bottom - const rect = new Graphene.Rect(); - rect.origin.x = this._viewport.x; - rect.origin.y = this._viewport.y; - rect.size.width = this._viewport.width; - rect.size.height = this._viewport.height; + // close windows on this monitor + const windows = global.display.get_tab_list(0, null).filter( + w => w.get_monitor() === this.monitorIndex && w.get_workspace() === this.metaWorkspace + ); - this._bgManager.backgroundActor.content.set_rounded_clip_bounds(rect);*/ + for (let i = 0; i < windows.length; i++) { + if (!windows[i].is_on_all_workspaces()) + windows[i].delete(global.get_current_time() + i); } }, - activate: function(time) { + activate(time) { if (this.state > ThumbnailState.NORMAL) return; @@ -199,112 +288,214 @@ var WorkspaceThumbnailCommon = { } else { this.metaWorkspace.activate(time); } - } else { - if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE && wsIndex < lastWsIndex) { - if (stateAdjustment.value > 1) { - stateAdjustment.value = 1; - } + } else if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE && wsIndex < lastWsIndex) { + if (stateAdjustment.value > 1) + stateAdjustment.value = 1; - // spread windows - // in OVERVIEW MODE 2 windows are not spread and workspace is not scaled - // we need to repeat transition to the overview state 1 (window picker), but with spreading windows animation - if (this.metaWorkspace.active) { - opt.WORKSPACE_MODE = 1; - const stateAdjustment = Main.overview._overview.controls._stateAdjustment - // setting value to 0 would reset WORKSPACE_MODE - stateAdjustment.value = 0.01; - stateAdjustment.ease(1, { - duration: 200, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - /*const adjustment = Main.overview._overview.controls._workspacesDisplay._workspacesViews[0]._workspaces[wsIndex].stateAdjustment; - adjustment.value = 0; - adjustment.ease(1, { - duration: 200, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - });*/ - } else { - // switch ws - this.metaWorkspace.activate(time); - } - // a click on the current workspace should go back to the main view - } else if (this.metaWorkspace.active) { - Main.overview.hide(); + // spread windows + // in OVERVIEW MODE 2 windows are not spread and workspace is not scaled + // we need to repeat transition to the overview state 1 (window picker), but with spreading windows animation + if (this.metaWorkspace.active) { + Main.overview._overview.controls._searchController._setSearchActive(false); + opt.WORKSPACE_MODE = 1; + // setting value to 0 would reset WORKSPACE_MODE + stateAdjustment.value = 0.01; + stateAdjustment.ease(1, { + duration: 200, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); } else { + // switch ws this.metaWorkspace.activate(time); } + // a click on the current workspace should go back to the main view + } else if (this.metaWorkspace.active) { + Main.overview.hide(); + } else { + this.metaWorkspace.activate(time); } - } -} + }, -// ThumbnailsBox Vertical + // Draggable target interface used only by ThumbnailsBox + handleDragOverInternal(source, actor, time) { + if (source === Main.xdndHandler) { + this.metaWorkspace.activate(time); + return DND.DragMotionResult.CONTINUE; + } -var ThumbnailsBoxVertical = { - _activateThumbnailAtPoint: function(stageX, stageY, time) { - const [r_, x, y] = this.transform_stage_point(stageX, stageY); + if (this.state > ThumbnailState.NORMAL) + return DND.DragMotionResult.CONTINUE; - const thumbnail = this._thumbnails.find(t => y >= t.y && y <= t.y + t.height); - if (thumbnail) { - thumbnail.activate(time); - } - }, + if (source.metaWindow && + !this._isMyWindow(source.metaWindow.get_compositor_private())) + return DND.DragMotionResult.MOVE_DROP; + if (source.app && source.app.can_open_new_window()) + return DND.DragMotionResult.COPY_DROP; + if (!source.app && source.shellWorkspaceLaunch) + return DND.DragMotionResult.COPY_DROP; - _getPlaceholderTarget: function(index, spacing, rtl) { - const workspace = this._thumbnails[index]; + if (source instanceof imports.ui.appDisplay.FolderIcon) + return DND.DragMotionResult.COPY_DROP; - let targetY1; - let targetY2; - if (rtl) { - const baseY = workspace.y + workspace.height; - targetY1 = baseY - WORKSPACE_CUT_SIZE; - targetY2 = baseY + spacing + WORKSPACE_CUT_SIZE; - } else { - targetY1 = workspace.y - spacing - WORKSPACE_CUT_SIZE; - targetY2 = workspace.y + WORKSPACE_CUT_SIZE; - } + return DND.DragMotionResult.CONTINUE; + }, - if (index === 0) { - if (rtl) - targetY2 -= spacing + WORKSPACE_CUT_SIZE; - else - targetY1 += spacing + WORKSPACE_CUT_SIZE; + acceptDropInternal(source, actor, time) { + if (this.state > ThumbnailState.NORMAL) + return false; + + if (source.metaWindow) { + let win = source.metaWindow.get_compositor_private(); + if (this._isMyWindow(win)) + return false; + + let metaWindow = win.get_meta_window(); + Main.moveWindowToMonitorAndWorkspace(metaWindow, + this.monitorIndex, this.metaWorkspace.index()); + return true; + } else if (source.app && source.app.can_open_new_window()) { + if (source.animateLaunchAtPos) + source.animateLaunchAtPos(actor.x, actor.y); + + source.app.open_new_window(this.metaWorkspace.index()); + return true; + } else if (!source.app && source.shellWorkspaceLaunch) { + // While unused in our own drag sources, shellWorkspaceLaunch allows + // extensions to define custom actions for their drag sources. + source.shellWorkspaceLaunch({ + workspace: this.metaWorkspace.index(), + timestamp: time, + }); + return true; + } else if (source instanceof imports.ui.appDisplay.FolderIcon) { + if (shellVersion >= 44) { + for (let app of source.view._apps) { + // const app = Shell.AppSystem.get_default().lookup_app(id); + app.open_new_window(this.metaWorkspace.index()); + } + } else { + for (let id of source.view._appIds) { + const app = Shell.AppSystem.get_default().lookup_app(id); + app.open_new_window(this.metaWorkspace.index()); + } + } } - if (index === this._dropPlaceholderPos) { - const placeholderHeight = this._dropPlaceholder.get_height() + spacing; - if (rtl) - targetY2 += placeholderHeight; - else - targetY1 -= placeholderHeight; + return false; + }, +}; + +const ThumbnailsBoxCommon = { + after__init(scrollAdjustment, monitorIndex, orientation = opt.ORIENTATION) { + this._boxOrientation = orientation; + }, + + _activateThumbnailAtPoint(stageX, stageY, time, activateCurrent = false) { + if (activateCurrent) { + const thumbnail = this._thumbnails.find(t => t.metaWorkspace.active); + if (thumbnail) + thumbnail.activate(time); + return; } + const [r_, x, y] = this.transform_stage_point(stageX, stageY); - return [targetY1, targetY2]; + let thumbnail; + + if (this._boxOrientation) + thumbnail = this._thumbnails.find(t => y >= t.y && y <= t.y + t.height); + else + thumbnail = this._thumbnails.find(t => x >= t.x && x <= t.x + t.width); + + if (thumbnail) + thumbnail.activate(time); }, - _withinWorkspace: function(y, index, rtl) { - const length = this._thumbnails.length; - const workspace = this._thumbnails[index]; + acceptDrop(source, actor, x, y, time) { + if (this._dropWorkspace !== -1) { + return this._thumbnails[this._dropWorkspace].acceptDropInternal(source, actor, time); + } else if (this._dropPlaceholderPos !== -1) { + if (!source.metaWindow && + (!source.app || !source.app.can_open_new_window()) && + (source.app || !source.shellWorkspaceLaunch) && + !(source instanceof imports.ui.appDisplay.FolderIcon)) + return false; + + + let isWindow = !!source.metaWindow; + + let newWorkspaceIndex; + [newWorkspaceIndex, this._dropPlaceholderPos] = [this._dropPlaceholderPos, -1]; + this._spliceIndex = newWorkspaceIndex; + + Main.wm.insertWorkspace(newWorkspaceIndex); + + if (isWindow) { + // Move the window to our monitor first if necessary. + let thumbMonitor = this._thumbnails[newWorkspaceIndex].monitorIndex; + Main.moveWindowToMonitorAndWorkspace(source.metaWindow, + thumbMonitor, newWorkspaceIndex, true); + } else if (source.app && source.app.can_open_new_window()) { + if (source.animateLaunchAtPos) + source.animateLaunchAtPos(actor.x, actor.y); + + source.app.open_new_window(newWorkspaceIndex); + } else if (!source.app && source.shellWorkspaceLaunch) { + // While unused in our own drag sources, shellWorkspaceLaunch allows + // extensions to define custom actions for their drag sources. + source.shellWorkspaceLaunch({ + workspace: newWorkspaceIndex, + timestamp: time, + }); + } else if (source instanceof imports.ui.appDisplay.FolderIcon) { + if (shellVersion >= 44) { + for (let app of source.view._apps) { + // const app = Shell.AppSystem.get_default().lookup_app(id); + app.open_new_window(newWorkspaceIndex); + } + } else { + for (let id of source.view._appIds) { + const app = Shell.AppSystem.get_default().lookup_app(id); + app.open_new_window(newWorkspaceIndex); + } + } + } - let workspaceY1 = workspace.y + WORKSPACE_CUT_SIZE; - let workspaceY2 = workspace.y + workspace.height - WORKSPACE_CUT_SIZE; + if (source.app || (!source.app && source.shellWorkspaceLaunch)) { + // This new workspace will be automatically removed if the application fails + // to open its first window within some time, as tracked by Shell.WindowTracker. + // Here, we only add a very brief timeout to avoid the _immediate_ removal of the + // workspace while we wait for the startup sequence to load. + let workspaceManager = global.workspace_manager; + Main.wm.keepWorkspaceAlive(workspaceManager.get_workspace_by_index(newWorkspaceIndex), + WorkspaceThumbnail.WORKSPACE_KEEP_ALIVE_TIME); + } - if (index === length - 1) { - if (rtl) - workspaceY1 -= WORKSPACE_CUT_SIZE; - else - workspaceY2 += WORKSPACE_CUT_SIZE; - } + // Start the animation on the workspace (which is actually + // an old one which just became empty) + let thumbnail = this._thumbnails[newWorkspaceIndex]; + this._setThumbnailState(thumbnail, ThumbnailState.NEW); + thumbnail.slide_position = 1; + thumbnail.collapse_fraction = 1; - return y > workspaceY1 && y <= workspaceY2; + this._queueUpdateStates(); + + return true; + } else { + return false; + } }, - handleDragOver: function(source, actor, x, y, time) { + handleDragOver(source, actor, x, y, time) { + // switch axis for vertical orientation + if (this._boxOrientation) + x = y; + if (!source.metaWindow && (!source.app || !source.app.can_open_new_window()) && (source.app || !source.shellWorkspaceLaunch) && - source != Main.xdndHandler) + source !== Main.xdndHandler && !(source instanceof imports.ui.appDisplay.FolderIcon)) return DND.DragMotionResult.CONTINUE; const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL; @@ -321,34 +512,132 @@ var ThumbnailsBoxVertical = { const [targetStart, targetEnd] = this._getPlaceholderTarget(index, spacing, rtl); - if (y > targetStart && y <= targetEnd) { + if (x > targetStart && x <= targetEnd) { placeholderPos = index; break; } } - if (this._withinWorkspace(y, index, rtl)) { + if (this._withinWorkspace(x, index, rtl)) { this._dropWorkspace = index; break; } } - if (this._dropPlaceholderPos != placeholderPos) { + if (this._dropPlaceholderPos !== placeholderPos) { this._dropPlaceholderPos = placeholderPos; this.queue_relayout(); } - if (this._dropWorkspace != -1) + if (this._dropWorkspace !== -1) return this._thumbnails[this._dropWorkspace].handleDragOverInternal(source, actor, time); - else if (this._dropPlaceholderPos != -1) + else if (this._dropPlaceholderPos !== -1) return source.metaWindow ? DND.DragMotionResult.MOVE_DROP : DND.DragMotionResult.COPY_DROP; else return DND.DragMotionResult.CONTINUE; }, - //vfunc_get_preferred_width: function(forHeight) { + _getPlaceholderTarget(...args) { + if (this._boxOrientation) + return ThumbnailsBoxVertical._getPlaceholderTarget.bind(this)(...args); + else + return ThumbnailsBoxHorizontal._getPlaceholderTarget.bind(this)(...args); + }, + + _withinWorkspace(...args) { + if (this._boxOrientation) + return ThumbnailsBoxVertical._withinWorkspace.bind(this)(...args); + else + return ThumbnailsBoxHorizontal._withinWorkspace.bind(this)(...args); + }, + + get_preferred_custom_width(...args) { + if (this._boxOrientation) + return ThumbnailsBoxVertical.get_preferred_custom_width.bind(this)(...args); + else + return ThumbnailsBoxHorizontal.get_preferred_custom_width.bind(this)(...args); + }, + + get_preferred_custom_height(...args) { + if (this._boxOrientation) + return ThumbnailsBoxVertical.get_preferred_custom_height.bind(this)(...args); + else + return ThumbnailsBoxHorizontal.get_preferred_custom_height.bind(this)(...args); + }, + + vfunc_allocate(...args) { + if (this._boxOrientation) + return ThumbnailsBoxVertical.vfunc_allocate.bind(this)(...args); + else + return ThumbnailsBoxHorizontal.vfunc_allocate.bind(this)(...args); + }, + + _updateShouldShow(...args) { + if (this._boxOrientation) + return ThumbnailsBoxVertical._updateShouldShow.bind(this)(...args); + else + return ThumbnailsBoxHorizontal._updateShouldShow.bind(this)(...args); + }, +}; + +const ThumbnailsBoxVertical = { + _getPlaceholderTarget(index, spacing, rtl) { + this._dropPlaceholder.add_style_class_name('placeholder-vertical'); + const workspace = this._thumbnails[index]; + + let targetY1; + let targetY2; + + if (rtl) { + const baseY = workspace.y + workspace.height; + targetY1 = baseY - WORKSPACE_CUT_SIZE; + targetY2 = baseY + spacing + WORKSPACE_CUT_SIZE; + } else { + targetY1 = workspace.y - spacing - WORKSPACE_CUT_SIZE; + targetY2 = workspace.y + WORKSPACE_CUT_SIZE; + } + + if (index === 0) { + if (rtl) + targetY2 -= spacing + WORKSPACE_CUT_SIZE; + else + targetY1 += spacing + WORKSPACE_CUT_SIZE; + } + + if (index === this._dropPlaceholderPos) { + const placeholderHeight = this._dropPlaceholder.get_height() + spacing; + if (rtl) + targetY2 += placeholderHeight; + else + targetY1 -= placeholderHeight; + } + + return [targetY1, targetY2]; + }, + + _withinWorkspace(y, index, rtl) { + const length = this._thumbnails.length; + const workspace = this._thumbnails[index]; + + let workspaceY1 = workspace.y + WORKSPACE_CUT_SIZE; + let workspaceY2 = workspace.y + workspace.height - WORKSPACE_CUT_SIZE; + + if (index === length - 1) { + if (rtl) + workspaceY1 -= WORKSPACE_CUT_SIZE; + else + workspaceY2 += WORKSPACE_CUT_SIZE; + } + + return y > workspaceY1 && y <= workspaceY2; + }, + + // vfunc_get_preferred_width: function(forHeight) { // override of this vfunc doesn't work for some reason (tested on Ubuntu and Fedora), it's not reachable - get_preferred_custom_width: function(forHeight) { + get_preferred_custom_width(forHeight) { + if (!this.visible) + return [0, 0]; + if (forHeight === -1) return this.get_preferred_custom_height(forHeight); @@ -363,14 +652,16 @@ var ThumbnailsBoxVertical = { const avail = forHeight - totalSpacing; let scale = (avail / nWorkspaces) / this._porthole.height; - scale = Math.min(scale, MAX_THUMBNAIL_SCALE); + // scale = Math.min(scale, opt.MAX_THUMBNAIL_SCALE); const width = Math.round(this._porthole.width * scale); - - return themeNode.adjust_preferred_width(width, width); + return themeNode.adjust_preferred_height(width, width); }, - get_preferred_custom_height: function(_forWidth) { + get_preferred_custom_height(_forWidth) { + if (!this.visible) + return [0, 0]; + // Note that for getPreferredHeight/Width we cheat a bit and skip propagating // the size request to our children because we know how big they are and know // that the actors aren't depending on the virtual functions being called. @@ -379,34 +670,29 @@ var ThumbnailsBoxVertical = { let spacing = themeNode.get_length('spacing'); let nWorkspaces = this._thumbnails.length; - let totalSpacing = (nWorkspaces - 1) * spacing; + // remove also top/bottom box padding + let totalSpacing = (nWorkspaces - 3) * spacing; const ratio = this._porthole.width / this._porthole.height; - const tmbHeight = _forWidth / ratio; - - const naturalheight = this._thumbnails.reduce((accumulator, thumbnail, index) => { - //let workspaceSpacing = 0; + const tmbHeight = themeNode.adjust_for_width(_forWidth) / ratio; + const naturalheight = this._thumbnails.reduce((accumulator, thumbnail/* , index*/) => { const progress = 1 - thumbnail.collapse_fraction; - //const height = (this._porthole.height * MAX_THUMBNAIL_SCALE + workspaceSpacing) * progress; - const height = (tmbHeight) * progress; + const height = tmbHeight * progress; return accumulator + height; }, 0); - //return themeNode.adjust_preferred_height(totalSpacing, naturalheight); - // we need to calculate the height precisely as it need to align with the workspacesDisplay because of transition animation - // This works perfectly for fullHD monitor, for some reason 5:4 aspect ratio monitor adds unnecessary pixels to the final height of the thumbnailsBox - return [totalSpacing, naturalheight]; + return themeNode.adjust_preferred_width(totalSpacing, naturalheight); }, // removes extra space (extraWidth in the original function), we need the box as accurate as possible // for precise app grid transition animation - vfunc_allocate: function(box) { + vfunc_allocate(box) { this.set_allocation(box); - let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; + let rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL; - if (this._thumbnails.length == 0) // not visible + if (this._thumbnails.length === 0) // not visible return; let themeNode = this.get_theme_node(); @@ -416,16 +702,19 @@ var ThumbnailsBoxVertical = { const portholeHeight = this._porthole.height; const spacing = themeNode.get_length('spacing'); - const nWorkspaces = this._thumbnails.length; + /* const nWorkspaces = this._thumbnails.length;*/ // Compute the scale we'll need once everything is updated, // unless we are currently transitioning if (this._expandFraction === 1) { - const totalSpacing = (nWorkspaces - 1) * spacing; - const availableHeight = (box.get_height() - totalSpacing) / nWorkspaces; + // remove size "breathing" during adding/removing workspaces + + /* const totalSpacing = (nWorkspaces - 1) * spacing; + const availableHeight = (box.get_height() - totalSpacing) / nWorkspaces; */ const hScale = box.get_width() / portholeWidth; - const vScale = availableHeight / portholeHeight; + /* const vScale = availableHeight / portholeHeight;*/ + const vScale = box.get_height() / portholeHeight; const newScale = Math.min(hScale, vScale); if (newScale !== this._targetScale) { @@ -466,13 +755,20 @@ var ThumbnailsBoxVertical = { let y = box.y1; - if (this._dropPlaceholderPos == -1) { + if (this._dropPlaceholderPos === -1) { this._dropPlaceholder.allocate_preferred_size( ...this._dropPlaceholder.get_position()); - Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { - this._dropPlaceholder.hide(); - }); + if (shellVersion >= 44) { + const laters = global.compositor.get_laters(); + laters.add(Meta.LaterType.BEFORE_REDRAW, () => { + this._dropPlaceholder.hide(); + }); + } else { + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { + this._dropPlaceholder.hide(); + }); + } } let childBox = new Clutter.ActorBox(); @@ -486,7 +782,7 @@ var ThumbnailsBoxVertical = { const x2 = x1 + thumbnailWidth; if (i === this._dropPlaceholderPos) { - let [, placeholderHeight] = this._dropPlaceholder.get_preferred_height(-1); + let [, placeholderHeight] = this._dropPlaceholder.get_preferred_width(-1); childBox.x1 = x1; childBox.x2 = x2; @@ -500,9 +796,16 @@ var ThumbnailsBoxVertical = { this._dropPlaceholder.allocate(childBox); - Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { - this._dropPlaceholder.show(); - }); + if (shellVersion >= 44) { + const laters = global.compositor.get_laters(); + laters.add(Meta.LaterType.BEFORE_REDRAW, () => { + this._dropPlaceholder.show(); + }); + } else { + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { + this._dropPlaceholder.show(); + }); + } y += placeholderHeight + spacing; } @@ -559,51 +862,119 @@ var ThumbnailsBoxVertical = { this._indicator.allocate(childBox); }, - _updateShouldShow: function() { - // set current workspace indicator border radius - // here just 'cause it's easier than adding to init - this._indicator.add_style_class_name('ws-tmb'); - + _updateShouldShow() { const shouldShow = opt.SHOW_WS_TMB; if (this._shouldShow === shouldShow) return; this._shouldShow = shouldShow; this.notify('should-show'); - } -} + }, +}; // ThumbnailsBox Horizontal -var ThumbnailsBoxHorizontal = { - get_preferred_custom_width: function(_forHeight) { +const ThumbnailsBoxHorizontal = { + _getPlaceholderTarget(index, spacing, rtl) { + const workspace = this._thumbnails[index]; + + let targetX1; + let targetX2; + + if (rtl) { + const baseX = workspace.x + workspace.width; + targetX1 = baseX - WORKSPACE_CUT_SIZE; + targetX2 = baseX + spacing + WORKSPACE_CUT_SIZE; + } else { + targetX1 = workspace.x - spacing - WORKSPACE_CUT_SIZE; + targetX2 = workspace.x + WORKSPACE_CUT_SIZE; + } + + if (index === 0) { + if (rtl) + targetX2 -= spacing + WORKSPACE_CUT_SIZE; + else + targetX1 += spacing + WORKSPACE_CUT_SIZE; + } + + if (index === this._dropPlaceholderPos) { + const placeholderWidth = this._dropPlaceholder.get_width() + spacing; + if (rtl) + targetX2 += placeholderWidth; + else + targetX1 -= placeholderWidth; + } + + return [targetX1, targetX2]; + }, + + _withinWorkspace(x, index, rtl) { + const length = this._thumbnails.length; + const workspace = this._thumbnails[index]; + + let workspaceX1 = workspace.x + WORKSPACE_CUT_SIZE; + let workspaceX2 = workspace.x + workspace.width - WORKSPACE_CUT_SIZE; + + if (index === length - 1) { + if (rtl) + workspaceX1 -= WORKSPACE_CUT_SIZE; + else + workspaceX2 += WORKSPACE_CUT_SIZE; + } + + return x > workspaceX1 && x <= workspaceX2; + }, + + get_preferred_custom_height(forWidth) { + let themeNode = this.get_theme_node(); + + forWidth = themeNode.adjust_for_width(forWidth); + + let spacing = themeNode.get_length('spacing'); + let nWorkspaces = this._thumbnails.length; + let totalSpacing = (nWorkspaces - 1) * spacing; + + const avail = forWidth - totalSpacing; + + let scale = (avail / nWorkspaces) / this._porthole.width; + // scale = Math.min(scale, opt.MAX_THUMBNAIL_SCALE); + + const height = Math.round(this._porthole.height * scale); + return themeNode.adjust_preferred_height(height, height); + }, + + get_preferred_custom_width(_forHeight) { // Note that for getPreferredHeight/Width we cheat a bit and skip propagating // the size request to our children because we know how big they are and know // that the actors aren't depending on the virtual functions being called. + if (!this.visible) + return [0, 0]; + let themeNode = this.get_theme_node(); let spacing = themeNode.get_length('spacing'); let nWorkspaces = this._thumbnails.length; - let totalSpacing = (nWorkspaces - 1) * spacing; + // remove also left/right box padding from the total spacing + let totalSpacing = (nWorkspaces - 3) * spacing; const ratio = this._porthole.height / this._porthole.width; - const tmbWidth = (_forHeight - 2 * spacing) / ratio; - const naturalWidth = this._thumbnails.reduce((accumulator, thumbnail, index) => { + const tmbWidth = themeNode.adjust_for_height(_forHeight) / ratio; + + const naturalWidth = this._thumbnails.reduce((accumulator, thumbnail) => { const progress = 1 - thumbnail.collapse_fraction; const width = tmbWidth * progress; return accumulator + width; }, 0); - return themeNode.adjust_preferred_width(totalSpacing, naturalWidth); }, vfunc_allocate(box) { this.set_allocation(box); - let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; + let rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL; - if (this._thumbnails.length == 0) // not visible + if (this._thumbnails.length === 0) // not visible return; let themeNode = this.get_theme_node(); @@ -613,15 +984,18 @@ var ThumbnailsBoxHorizontal = { const portholeHeight = this._porthole.height; const spacing = themeNode.get_length('spacing'); - const nWorkspaces = this._thumbnails.length; + /* const nWorkspaces = this._thumbnails.length; */ // Compute the scale we'll need once everything is updated, // unless we are currently transitioning if (this._expandFraction === 1) { - const totalSpacing = (nWorkspaces - 1) * spacing; + // remove size "breathing" during adding/removing workspaces + + /* const totalSpacing = (nWorkspaces - 1) * spacing; const availableWidth = (box.get_width() - totalSpacing) / nWorkspaces; - const hScale = availableWidth / portholeWidth; + const hScale = availableWidth / portholeWidth; */ + const hScale = box.get_width() / portholeWidth; const vScale = box.get_height() / portholeHeight; const newScale = Math.min(hScale, vScale); @@ -663,13 +1037,20 @@ var ThumbnailsBoxHorizontal = { let x = box.x1; - if (this._dropPlaceholderPos == -1) { + if (this._dropPlaceholderPos === -1) { this._dropPlaceholder.allocate_preferred_size( ...this._dropPlaceholder.get_position()); - Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { - this._dropPlaceholder.hide(); - }); + if (shellVersion >= 44) { + const laters = global.compositor.get_laters(); + laters.add(Meta.LaterType.BEFORE_REDRAW, () => { + this._dropPlaceholder.hide(); + }); + } else { + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { + this._dropPlaceholder.hide(); + }); + } } let childBox = new Clutter.ActorBox(); @@ -697,9 +1078,16 @@ var ThumbnailsBoxHorizontal = { this._dropPlaceholder.allocate(childBox); - Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { - this._dropPlaceholder.show(); - }); + if (shellVersion >= 44) { + const laters = global.compositor.get_laters(); + laters.add(Meta.LaterType.BEFORE_REDRAW, () => { + this._dropPlaceholder.show(); + }); + } else { + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { + this._dropPlaceholder.show(); + }); + } x += placeholderWidth + spacing; } @@ -756,5 +1144,5 @@ var ThumbnailsBoxHorizontal = { this._indicator.allocate(childBox); }, - _updateShouldShow: ThumbnailsBoxVertical._updateShouldShow -} + _updateShouldShow: ThumbnailsBoxVertical._updateShouldShow, +}; diff --git a/extensions/vertical-workspaces/workspacesView.js b/extensions/vertical-workspaces/lib/workspacesView.js index 820ea6b..e3575f1 100644 --- a/extensions/vertical-workspaces/workspacesView.js +++ b/extensions/vertical-workspaces/lib/workspacesView.js @@ -1,7 +1,7 @@ /** - * Vertical Workspaces + * V-Shell (Vertical Workspaces) * workspacesView.js - * + * * @author GdH <G-dH@github.com> * @copyright 2022 - 2023 * @license GPL-3.0 @@ -15,27 +15,30 @@ const { GObject, Clutter, Meta, St } = imports.gi; const Main = imports.ui.main; const Util = imports.misc.util; const WorkspacesView = imports.ui.workspacesView; -//const SecondaryMonitorDisplay = WorkspacesView.SecondaryMonitorDisplay; -// first call of item defined using const in other module returns undefined -WorkspacesView.SecondaryMonitorDisplay; +// first reference to constant defined using const in other module returns undefined, the SecondaryMonitorDisplay const will remain empty and unused +const SecondaryMonitorDisplay = WorkspacesView.SecondaryMonitorDisplay; const ControlsState = imports.ui.overviewControls.ControlsState; const FitMode = imports.ui.workspacesView.FitMode; const SIDE_CONTROLS_ANIMATION_TIME = imports.ui.overview.ANIMATION_TIME; const Me = imports.misc.extensionUtils.getCurrentExtension(); -const SEARCH_WINDOWS_PREFIX = Me.imports.windowSearchProvider.prefix; -const SEARCH_RECENT_FILES_PREFIX = Me.imports.recentFilesSearchProvider.prefix; +const SEARCH_WINDOWS_PREFIX = Me.imports.lib.windowSearchProvider.prefix; +const SEARCH_RECENT_FILES_PREFIX = Me.imports.lib.recentFilesSearchProvider.prefix; -const _Util = Me.imports.util; +const _Util = Me.imports.lib.util; let _overrides; let opt; - function update(reset = false) { - if (_overrides) { + opt = Me.imports.lib.settings.opt; + opt.DESKTOP_CUBE_ENABLED = Main.extensionManager._enabledExtensions.includes('desktop-cube@schneegans.github.com'); + const cubeSupported = opt.DESKTOP_CUBE_ENABLED && !opt.ORIENTATION && !opt.OVERVIEW_MODE; + + // if desktop cube extension is enabled while V-Shell is loaded, removeAll() would override its code + if (_overrides && !cubeSupported) { _overrides.removeAll(); global.workspace_manager.override_workspace_layout(Meta.DisplayCorner.TOPLEFT, false, 1, -1); } @@ -46,12 +49,14 @@ function update(reset = false) { return; } - opt = Me.imports.settings.opt; _overrides = new _Util.Overrides(); - _overrides.addOverride('WorkspacesView', WorkspacesView.WorkspacesView.prototype, WorkspacesViewCommon); + if (!cubeSupported) + _overrides.addOverride('WorkspacesView', WorkspacesView.WorkspacesView.prototype, WorkspacesViewCommon); + _overrides.addOverride('WorkspacesDisplay', WorkspacesView.WorkspacesDisplay.prototype, WorkspacesDisplay); + _overrides.addOverride('ExtraWorkspaceView', WorkspacesView.ExtraWorkspaceView.prototype, ExtraWorkspaceView); if (opt.ORIENTATION) { // switch internal workspace orientation in GS @@ -62,8 +67,8 @@ function update(reset = false) { } } -var WorkspacesViewCommon = { - _getFirstFitSingleWorkspaceBox: function(box, spacing, vertical) { +const WorkspacesViewCommon = { + _getFirstFitSingleWorkspaceBox(box, spacing, vertical) { let [width, height] = box.get_size(); const [workspace] = this._workspaces; @@ -74,8 +79,8 @@ var WorkspacesViewCommon = { // Single fit mode implies centered too let [x1, y1] = box.get_origin(); - const [, workspaceWidth] = workspace ? workspace.get_preferred_width(Math.floor(height)) : [,width]; - const [, workspaceHeight] = workspace ? workspace.get_preferred_height(workspaceWidth) : [,height]; + const [, workspaceWidth] = workspace ? workspace.get_preferred_width(Math.floor(height)) : [0, width]; + const [, workspaceHeight] = workspace ? workspace.get_preferred_height(workspaceWidth) : [0, height]; if (vertical) { x1 += (width - workspaceWidth) / 2; @@ -85,7 +90,7 @@ var WorkspacesViewCommon = { x1 -= currentWorkspace * (workspaceWidth + spacing); } - const fitSingleBox = new Clutter.ActorBox({x1, y1}); + const fitSingleBox = new Clutter.ActorBox({ x1, y1 }); fitSingleBox.set_size(workspaceWidth, workspaceHeight); @@ -93,11 +98,12 @@ var WorkspacesViewCommon = { }, // set spacing between ws previews - _getSpacing: function(box, fitMode, vertical) { + _getSpacing(box, fitMode, vertical) { const [width, height] = box.get_size(); const [workspace] = this._workspaces; - if (!workspace) return; + if (!workspace) + return 0; let availableSpace; let workspaceSize; @@ -117,12 +123,12 @@ var WorkspacesViewCommon = { }, // this function has duplicate in OverviewControls so we use one function for both to avoid issues with syncing them - _getFitModeForState: function(state) { + _getFitModeForState(state) { return _getFitModeForState(state); }, // normal view 0, spread windows 1 - _getWorkspaceModeForOverviewState: function(state) { + _getWorkspaceModeForOverviewState(state) { switch (state) { case ControlsState.HIDDEN: @@ -130,14 +136,15 @@ var WorkspacesViewCommon = { case ControlsState.WINDOW_PICKER: return opt.WORKSPACE_MODE; case ControlsState.APP_GRID: - return ((this._monitorIndex !== global.display.get_primary_monitor() || !opt.WS_ANIMATION) && !opt.OVERVIEW_MODE) ? 1 : 0; + return (this._monitorIndex !== global.display.get_primary_monitor() || !opt.WS_ANIMATION) && !opt.OVERVIEW_MODE ? 1 : 0; } return 0; }, - _updateVisibility: function() { - let workspaceManager = global.workspace_manager; + _updateVisibility() { + // replaced in _updateWorkspacesState + /* let workspaceManager = global.workspace_manager; let active = workspaceManager.get_active_workspace_index(); const fitMode = this._fitModeAdjustment.value; @@ -146,17 +153,15 @@ var WorkspacesViewCommon = { for (let w = 0; w < this._workspaces.length; w++) { let workspace = this._workspaces[w]; - if (this._animating || this._gestureActive || !singleFitMode) { - //workspace.show(); - } else { + if (this._animating || this._gestureActive || !singleFitMode) + workspace.show(); + else workspace.visible = Math.abs(w - active) <= opt.NUMBER_OF_VISIBLE_NEIGHBORS; - } - - } + }*/ }, // disable scaling and hide inactive workspaces - _updateWorkspacesState: function() { + _updateWorkspacesState() { const adj = this._scrollAdjustment; const fitMode = this._fitModeAdjustment.value; @@ -168,12 +173,11 @@ var WorkspacesViewCommon = { this._getWorkspaceModeForOverviewState(finalState), progress); - - const currentMonitor = Main.layoutManager.primaryMonitor.index; + const primaryMonitor = Main.layoutManager.primaryMonitor.index; // define the transition values here to save time in each ws let scaleX, scaleY; - if (opt.ORIENTATION) { //vertical 1 / horizontal 0 + if (opt.ORIENTATION) { // vertical 1 / horizontal 0 scaleX = 1; scaleY = 0.1; } else { @@ -181,134 +185,143 @@ var WorkspacesViewCommon = { scaleY = 1; } + const wsScrollProgress = adj.value % 1; const secondaryMonitor = this._monitorIndex !== global.display.get_primary_monitor(); - const blockSecondaryAppGrid = opt.OVERVIEW_MODE && currentState >= 1; + const blockSecondaryAppGrid = opt.OVERVIEW_MODE && currentState > 1; // Hide inactive workspaces this._workspaces.forEach((w, index) => { if (!(blockSecondaryAppGrid && secondaryMonitor)) w.stateAdjustment.value = workspaceMode; - //w.stateAdjustment.value = workspaceMode; const distanceToCurrentWorkspace = Math.abs(adj.value - index); const scaleProgress = 1 - Math.clamp(distanceToCurrentWorkspace, 0, 1); // if we disable workspaces that we can't or don't need to see, transition animations will be noticeably smoother - // only the current ws needs to be visible during overview transition animations // and only current and adjacent ws when switching ws - if (opt.WORKSPACE_MAX_SPACING > 340) { // large spacing - only one workspace needs to be visible at once in the overview - w.visible = scaleProgress || ((currentState % 1) && !distanceToCurrentWorkspace); - - // horizontal orientation - 2 adjacent workspaces can be visible on the screen with the current one - // in order to keep animations as smooth as possible, hide all ws that cannot/shouldn't be visible at the given time - } else { - // - w.visible = w.monitorIndex !== currentMonitor || scaleProgress || (!opt.WS_ANIMATION && distanceToCurrentWorkspace < opt.NUMBER_OF_VISIBLE_NEIGHBORS) - || (distanceToCurrentWorkspace < opt.NUMBER_OF_VISIBLE_NEIGHBORS && currentState <= ControlsState.WINDOW_PICKER - && ((initialState < ControlsState.APP_GRID && finalState < ControlsState.APP_GRID)) - ); - - // after transition from APP_GRID to WINDOW_PICKER state, - // adjacent workspaces are hidden and we need them to show up - // make them visible during animation can impact smoothness of the animation - // so we show them after the animation finished, scaling animation will make impression that they move in from outside the monitor - if (!w.visible && distanceToCurrentWorkspace <= opt.NUMBER_OF_VISIBLE_NEIGHBORS && currentState === ControlsState.WINDOW_PICKER) { - w.scale_x = scaleX; - w.scale_y = scaleY; - w.visible = true; - w.ease({ - duration: 100, - scale_x: 1, - scale_y: 1, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - } + w.visible = (this._animating && wsScrollProgress && distanceToCurrentWorkspace <= (opt.NUMBER_OF_VISIBLE_NEIGHBORS + 1)) || scaleProgress === 1 || + (opt.WORKSPACE_MAX_SPACING > 340 && distanceToCurrentWorkspace <= opt.NUMBER_OF_VISIBLE_NEIGHBORS && currentState === ControlsState.WINDOW_PICKER) || + (this._monitorIndex !== primaryMonitor && distanceToCurrentWorkspace <= opt.NUMBER_OF_VISIBLE_NEIGHBORS) || (!opt.WS_ANIMATION && distanceToCurrentWorkspace < opt.NUMBER_OF_VISIBLE_NEIGHBORS) || + (opt.WORKSPACE_MAX_SPACING < 340 && distanceToCurrentWorkspace <= opt.NUMBER_OF_VISIBLE_NEIGHBORS && currentState <= ControlsState.WINDOW_PICKER && + ((initialState < ControlsState.APP_GRID && finalState < ControlsState.APP_GRID)) + ); + + // after transition from APP_GRID to WINDOW_PICKER state, + // adjacent workspaces are hidden and we need them to show up + // make them visible during animation can impact smoothness of the animation + // so we show them after the animation finished, scaling animation will make impression that they move in from outside the monitor + if (!w.visible && distanceToCurrentWorkspace === 1 && initialState === ControlsState.APP_GRID && currentState === ControlsState.WINDOW_PICKER) { + w.scale_x = scaleX; + w.scale_y = scaleY; + w.visible = true; + w.ease({ + duration: 100, + scale_x: 1, + scale_y: 1, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } else if (!w.visible && distanceToCurrentWorkspace <= opt.NUMBER_OF_VISIBLE_NEIGHBORS && currentState === ControlsState.WINDOW_PICKER) { + w.set({ + scale_x: 1, + scale_y: 1, + }); } // force ws preview bg corner radiuses where GS doesn't do it - if (opt.SHOW_WS_PREVIEW_BG && opt.OVERVIEW_MODE === 1 && distanceToCurrentWorkspace < 2) { + if (opt.SHOW_WS_PREVIEW_BG && opt.OVERVIEW_MODE === 1 && distanceToCurrentWorkspace < 2) w._background._updateBorderRadius(Math.min(1, w._overviewAdjustment.value)); - } + // hide workspace background - if (!opt.SHOW_WS_PREVIEW_BG && w._background.opacity) { + if (!opt.SHOW_WS_PREVIEW_BG && w._background.opacity) w._background.opacity = 0; - } }); - } -} + }, +}; // SecondaryMonitorDisplay Vertical -var SecondaryMonitorDisplayVertical = { - _getThumbnailParamsForState: function(state) { +const SecondaryMonitorDisplayVertical = { + _getThumbnailParamsForState(state) { - let opacity, scale, translation_x; + let opacity, scale, translationX; switch (state) { case ControlsState.HIDDEN: opacity = 255; scale = 1; - translation_x = 0; - if (!Main.layoutManager._startingUp && (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2)) { - translation_x = this._thumbnails.width * (opt.SEC_WS_TMB_LEFT ? -1 : 1); - } + translationX = 0; + if (!Main.layoutManager._startingUp && (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2)) + translationX = this._thumbnails.width * (opt.SEC_WS_TMB_LEFT ? -1 : 1); + break; case ControlsState.WINDOW_PICKER: case ControlsState.APP_GRID: opacity = 255; scale = 1; - translation_x = 0; + translationX = 0; break; default: opacity = 255; scale = 1; - translation_x = 0; + translationX = 0; break; } - return { opacity, scale, translation_x }; + return { opacity, scale, translationX }; }, - _getThumbnailsWidth: function(box, spacing) { - if (!this._thumbnails.visible) + _getThumbnailsWidth(box, spacing) { + if (opt.SEC_WS_TMB_HIDDEN) return 0; const [width, height] = box.get_size(); const { expandFraction } = this._thumbnails; const [, thumbnailsWidth] = this._thumbnails.get_preferred_custom_width(height - 2 * spacing); + let scaledWidth; + if (opt.SEC_WS_PREVIEW_SHIFT && !opt.PANEL_DISABLED) + scaledWidth = ((height - Main.panel.height) * opt.SEC_MAX_THUMBNAIL_SCALE) * (width / height); + else + scaledWidth = width * opt.SEC_MAX_THUMBNAIL_SCALE; + return Math.min( thumbnailsWidth * expandFraction, - width * opt.MAX_THUMBNAIL_SCALE); + Math.round(scaledWidth)); }, - _getWorkspacesBoxForState: function(state, box, padding, thumbnailsWidth, spacing) { - //const { ControlsState } = OverviewControls; + _getWorkspacesBoxForState(state, box, padding, thumbnailsWidth, spacing) { + // const { ControlsState } = OverviewControls; const workspaceBox = box.copy(); const [width, height] = workspaceBox.get_size(); + let wWidth, wHeight, wsbX, wsbY, offset, yShift; switch (state) { case ControlsState.HIDDEN: break; case ControlsState.WINDOW_PICKER: case ControlsState.APP_GRID: - if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) { + if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) break; + + yShift = 0; + if (opt.SEC_WS_PREVIEW_SHIFT && !opt.PANEL_DISABLED) { + if (opt.PANEL_POSITION_TOP) + yShift = Main.panel.height; + else + yShift = -Main.panel.height; } - let wWidth = Math.round(width - thumbnailsWidth - 5 * spacing); - let wHeight = Math.round(Math.min(wWidth / (width / height), height - 1.7 * padding)); - wWidth *= opt.WS_PREVIEW_SCALE; - wHeight *= opt.WS_PREVIEW_SCALE; + wWidth = width - thumbnailsWidth - 5 * spacing; + wHeight = Math.min(wWidth / (width / height) - Math.abs(yShift), height - 4 * spacing); + wWidth = Math.round(wWidth * opt.SEC_WS_PREVIEW_SCALE); + wHeight = Math.round(wHeight * opt.SEC_WS_PREVIEW_SCALE); - let wsbX; - let offset = Math.round(width - thumbnailsWidth - wWidth) / 2; - if (opt.SEC_WS_TMB_LEFT) { + offset = Math.round(width - thumbnailsWidth - wWidth) / 2; + if (opt.SEC_WS_TMB_LEFT) wsbX = thumbnailsWidth + offset; - } else { + else wsbX = offset; - } - const wsbY = Math.round((height - wHeight) / 2); + wsbY = Math.round((height - wHeight - Math.abs(yShift)) / 2 + yShift); workspaceBox.set_origin(wsbX, wsbY); workspaceBox.set_size(wWidth, wHeight); @@ -318,7 +331,7 @@ var SecondaryMonitorDisplayVertical = { return workspaceBox; }, - vfunc_allocate: function(box) { + vfunc_allocate(box) { this.set_allocation(box); const themeNode = this.get_theme_node(); @@ -332,7 +345,7 @@ var SecondaryMonitorDisplayVertical = { let [, thumbnailsHeight] = this._thumbnails.get_preferred_custom_height(thumbnailsWidth); thumbnailsHeight = Math.min(thumbnailsHeight, height - 2 * spacing); - this._thumbnails.visible = opt.SHOW_WS_TMB; + this._thumbnails.visible = !opt.SEC_WS_TMB_HIDDEN; if (this._thumbnails.visible) { let wsTmbX; if (opt.SEC_WS_TMB_LEFT) { // left @@ -373,13 +386,12 @@ var SecondaryMonitorDisplayVertical = { this._workspacesView.allocate(workspacesBox); }, - _updateThumbnailVisibility: function() { - if (opt.OVERVIEW_MODE2) { + _updateThumbnailVisibility() { + if (opt.OVERVIEW_MODE2) this.set_child_above_sibling(this._thumbnails, null); - } - const visible = !(this._settings.get_boolean('workspaces-only-on-primary') || - opt.SEC_WS_TMB_POSITION === 3); // 3 - disabled + + const visible = !opt.SEC_WS_TMB_HIDDEN; if (this._thumbnails.visible === visible) return; @@ -396,15 +408,15 @@ var SecondaryMonitorDisplayVertical = { }); }, - _updateThumbnailParams: function() { + _updateThumbnailParams() { + if (opt.SEC_WS_TMB_HIDDEN) + return; + // workaround for upstream bug - secondary thumbnails boxes don't catch 'showing' signal on the shell startup and don't populate the box with thumbnails // the tmbBox contents is also destroyed when overview state adjustment gets above 1 when swiping gesture from window picker to app grid - if (!this._thumbnails._thumbnails.length) { + if (!this._thumbnails._thumbnails.length) this._thumbnails._createThumbnails(); - } - if (!this._thumbnails.visible) - return; const { initialState, finalState, progress } = this._overviewAdjustment.getStateTransitionParams(); @@ -412,113 +424,122 @@ var SecondaryMonitorDisplayVertical = { const initialParams = this._getThumbnailParamsForState(initialState); const finalParams = this._getThumbnailParamsForState(finalState); - /*const opacity = + /* const opacity = Util.lerp(initialParams.opacity, finalParams.opacity, progress); const scale = Util.lerp(initialParams.scale, finalParams.scale, progress);*/ // OVERVIEW_MODE 2 should animate dash and wsTmbBox only if WORKSPACE_MODE === 0 (windows not spread) const animateOverviewMode2 = opt.OVERVIEW_MODE2 && !(finalState === 1 && opt.WORKSPACE_MODE); - const translation_x = (!Main.layoutManager._startingUp && ((!opt.SHOW_WS_PREVIEW_BG && !(opt.OVERVIEW_MODE2)) || animateOverviewMode2)) - ? Util.lerp(initialParams.translation_x, finalParams.translation_x, progress) + const translationX = !Main.layoutManager._startingUp && ((!opt.SHOW_WS_PREVIEW_BG && !opt.OVERVIEW_MODE2) || animateOverviewMode2) + ? Util.lerp(initialParams.translationX, finalParams.translationX, progress) : 0; this._thumbnails.set({ opacity: 255, - //scale_x: scale, - //scale_y: scale, - translation_x, + // scale_x: scale, + // scale_y: scale, + translation_x: translationX, }); }, - _updateWorkspacesView: function() { + _updateWorkspacesView() { if (this._workspacesView) this._workspacesView.destroy(); if (this._settings.get_boolean('workspaces-only-on-primary')) { + opt.SEC_WS_TMB_HIDDEN = true; this._workspacesView = new WorkspacesView.ExtraWorkspaceView( this._monitorIndex, this._overviewAdjustment); } else { + opt.SEC_WS_TMB_HIDDEN = !opt.SHOW_SEC_WS_TMB; this._workspacesView = new WorkspacesView.WorkspacesView( this._monitorIndex, this._controls, this._scrollAdjustment, // Secondary monitors don't need FitMode.ALL since there is workspace switcher always visible - //this._fitModeAdjustment, + // this._fitModeAdjustment, new St.Adjustment({ actor: this, - value: 0,//FitMode.SINGLE, - lower: 0,//FitMode.SINGLE, - upper: 0,//FitMode.SINGLE, + value: 0, // FitMode.SINGLE, + lower: 0, // FitMode.SINGLE, + upper: 0, // FitMode.SINGLE, }), - //secondaryOverviewAdjustment); + // secondaryOverviewAdjustment); this._overviewAdjustment); } this.add_child(this._workspacesView); this._thumbnails.opacity = 0; - } -} + }, +}; // SecondaryMonitorDisplay Horizontal -var SecondaryMonitorDisplayHorizontal = { - _getThumbnailParamsForState: function(state) { - //const { ControlsState } = OverviewControls; +const SecondaryMonitorDisplayHorizontal = { + _getThumbnailParamsForState(state) { + // const { ControlsState } = OverviewControls; - let opacity, scale, translation_y; + let opacity, scale, translationY; switch (state) { case ControlsState.HIDDEN: opacity = 255; scale = 1; - translation_y = 0; - if (!Main.layoutManager._startingUp && (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2)) { - translation_y = this._thumbnails.height * (opt.SEC_WS_TMB_TOP ? -1 : 1); - } + translationY = 0; + if (!Main.layoutManager._startingUp && (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2)) + translationY = this._thumbnails.height * (opt.SEC_WS_TMB_TOP ? -1 : 1); + break; case ControlsState.WINDOW_PICKER: case ControlsState.APP_GRID: opacity = 255; scale = 1; - translation_y = 0; + translationY = 0; break; default: opacity = 255; scale = 1; - translation_y = 0; + translationY = 0; break; } - return { opacity, scale, translation_y }; + return { opacity, scale, translationY }; }, - _getWorkspacesBoxForState: function(state, box, padding, thumbnailsHeight, spacing) { - //const { ControlsState } = OverviewControls; + _getWorkspacesBoxForState(state, box, padding, thumbnailsHeight, spacing) { + // const { ControlsState } = OverviewControls; const workspaceBox = box.copy(); const [width, height] = workspaceBox.get_size(); + let wWidth, wHeight, wsbX, wsbY, offset, yShift; switch (state) { case ControlsState.HIDDEN: break; case ControlsState.WINDOW_PICKER: case ControlsState.APP_GRID: - if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) { + if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) break; + + yShift = 0; + if (opt.SEC_WS_PREVIEW_SHIFT && !opt.PANEL_DISABLED) { + if (opt.PANEL_POSITION_TOP) + yShift = Main.panel.height; + else + yShift = -Main.panel.height; } - let wHeight = Math.round(Math.min(height - thumbnailsHeight - 5 * spacing)); - let wWidth = Math.round(Math.min(wHeight * (width / height), width - 1.7 * padding)); - wWidth *= opt.WS_PREVIEW_SCALE; - wHeight *= opt.WS_PREVIEW_SCALE; + wHeight = height - Math.abs(yShift) - (thumbnailsHeight ? thumbnailsHeight + 4 * spacing : padding); + wWidth = Math.min(wHeight * (width / height), width - 5 * spacing); + wWidth = Math.round(wWidth * opt.SEC_WS_PREVIEW_SCALE); + wHeight = Math.round(wHeight * opt.SEC_WS_PREVIEW_SCALE); - let wsbY; - let offset = Math.round((height - thumbnailsHeight - wHeight) / 2); - if (opt.WS_TMB_TOP) { + offset = Math.round((height - thumbnailsHeight - wHeight - Math.abs(yShift)) / 2); + if (opt.SEC_WS_TMB_TOP) wsbY = thumbnailsHeight + offset; - } else { + else wsbY = offset; - } - const wsbX = Math.round((width - wWidth) / 2); + wsbY += yShift; + wsbX = Math.round((width - wWidth) / 2); workspaceBox.set_origin(wsbX, wsbY); workspaceBox.set_size(wWidth, wHeight); @@ -528,8 +549,8 @@ var SecondaryMonitorDisplayHorizontal = { return workspaceBox; }, - _getThumbnailsHeight: function(box) { - if (!this._thumbnails.visible) + _getThumbnailsHeight(box) { + if (opt.SEC_WS_TMB_HIDDEN) return 0; const [width, height] = box.get_size(); @@ -537,10 +558,10 @@ var SecondaryMonitorDisplayHorizontal = { const [thumbnailsHeight] = this._thumbnails.get_preferred_height(width); return Math.min( thumbnailsHeight * expandFraction, - height * opt.MAX_THUMBNAIL_SCALE); + height * opt.SEC_MAX_THUMBNAIL_SCALE); }, - vfunc_allocate: function(box) { + vfunc_allocate(box) { this.set_allocation(box); const themeNode = this.get_theme_node(); @@ -554,14 +575,14 @@ var SecondaryMonitorDisplayHorizontal = { let [, thumbnailsWidth] = this._thumbnails.get_preferred_custom_width(thumbnailsHeight); thumbnailsWidth = Math.min(thumbnailsWidth, width - 2 * spacing); - this._thumbnails.visible = opt.SHOW_WS_TMB; + this._thumbnails.visible = !opt.SEC_WS_TMB_HIDDEN; if (this._thumbnails.visible) { let wsTmbY; - if (opt.SEC_WS_TMB_TOP) { + if (opt.SEC_WS_TMB_TOP) wsTmbY = Math.round(spacing / 4); - } else { + else wsTmbY = Math.round(height - spacing / 4 - thumbnailsHeight); - } + const childBox = new Clutter.ActorBox(); const availSpace = width - thumbnailsWidth - 2 * spacing; @@ -595,15 +616,15 @@ var SecondaryMonitorDisplayHorizontal = { _updateThumbnailVisibility: SecondaryMonitorDisplayVertical._updateThumbnailVisibility, - _updateThumbnailParams: function() { + _updateThumbnailParams() { + if (opt.SEC_WS_TMB_HIDDEN) + return; + // workaround for upstream bug - secondary thumbnails boxes don't catch 'showing' signal on the shell startup and don't populate the box with thumbnails // the tmbBox contents is also destroyed when overview state adjustment gets above 1 when swiping gesture from window picker to app grid - if (!this._thumbnails._thumbnails.length) { + if (!this._thumbnails._thumbnails.length) this._thumbnails._createThumbnails(); - } - if (!this._thumbnails.visible) - return; const { initialState, finalState, progress } = this._overviewAdjustment.getStateTransitionParams(); @@ -611,56 +632,79 @@ var SecondaryMonitorDisplayHorizontal = { const initialParams = this._getThumbnailParamsForState(initialState); const finalParams = this._getThumbnailParamsForState(finalState); - /*const opacity = + /* const opacity = Util.lerp(initialParams.opacity, finalParams.opacity, progress); const scale = Util.lerp(initialParams.scale, finalParams.scale, progress);*/ // OVERVIEW_MODE 2 should animate dash and wsTmbBox only if WORKSPACE_MODE === 0 (windows not spread) const animateOverviewMode2 = opt.OVERVIEW_MODE2 && !(finalState === 1 && opt.WORKSPACE_MODE); - const translation_y = (!Main.layoutManager._startingUp && ((!opt.SHOW_WS_PREVIEW_BG && !(opt.OVERVIEW_MODE2)) || animateOverviewMode2)) - ? Util.lerp(initialParams.translation_y, finalParams.translation_y, progress) + const translationY = !Main.layoutManager._startingUp && ((!opt.SHOW_WS_PREVIEW_BG && !opt.OVERVIEW_MODE2) || animateOverviewMode2) + ? Util.lerp(initialParams.translationY, finalParams.translationY, progress) : 0; this._thumbnails.set({ opacity: 255, - //scale_x: scale, - //scale_y: scale, - translation_y, + // scale_x: scale, + // scale_y: scale, + translation_y: translationY, }); }, - _updateWorkspacesView: function() { + _updateWorkspacesView() { if (this._workspacesView) this._workspacesView.destroy(); if (this._settings.get_boolean('workspaces-only-on-primary')) { + opt.SEC_WS_TMB_HIDDEN = true; this._workspacesView = new WorkspacesView.ExtraWorkspaceView( this._monitorIndex, this._overviewAdjustment); } else { + opt.SEC_WS_TMB_HIDDEN = !opt.SHOW_SEC_WS_TMB; this._workspacesView = new WorkspacesView.WorkspacesView( this._monitorIndex, this._controls, this._scrollAdjustment, // Secondary monitors don't need FitMode.ALL since there is workspace switcher always visible - //this._fitModeAdjustment, + // this._fitModeAdjustment, new St.Adjustment({ actor: this, - value: 0,//FitMode.SINGLE, - lower: 0,//FitMode.SINGLE, - upper: 0,//FitMode.SINGLE, + value: 0, // FitMode.SINGLE, + lower: 0, // FitMode.SINGLE, + upper: 0, // FitMode.SINGLE, }), - //secondaryOverviewAdjustment); + // secondaryOverviewAdjustment); this._overviewAdjustment); } this.add_child(this._workspacesView); this._thumbnails.opacity = 0; - } -} + }, +}; + +const ExtraWorkspaceView = { + _updateWorkspaceMode() { + const overviewState = this._overviewAdjustment.value; -var WorkspacesDisplay = { - _updateWorkspacesViews: function() { + const progress = Math.clamp(overviewState, + ControlsState.HIDDEN, + opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE ? ControlsState.HIDDEN : ControlsState.WINDOW_PICKER); + + this._workspace.stateAdjustment.value = progress; + + // force ws preview bg corner radiuses where GS doesn't do it + if (opt.SHOW_WS_PREVIEW_BG && opt.OVERVIEW_MODE === 1) + this._workspace._background._updateBorderRadius(Math.min(1, this._workspace._overviewAdjustment.value)); + + + // hide workspace background + if (!opt.SHOW_WS_PREVIEW_BG && this._workspace._background.opacity) + this._workspace._background.opacity = 0; + }, +}; + +const WorkspacesDisplay = { + _updateWorkspacesViews() { for (let i = 0; i < this._workspacesViews.length; i++) this._workspacesViews[i].destroy(); @@ -684,12 +728,12 @@ var WorkspacesDisplay = { this._controls, this._scrollAdjustment, // Secondary monitors don't need FitMode.ALL since there is workspace switcher always visible - //this._fitModeAdjustment, + // this._fitModeAdjustment, new St.Adjustment({ actor: this, - value: 0,//FitMode.SINGLE, - lower: 0,//FitMode.SINGLE, - upper: 0,//FitMode.SINGLE, + value: 0, // FitMode.SINGLE, + lower: 0, // FitMode.SINGLE, + upper: 0, // FitMode.SINGLE, }), this._overviewAdjustment); Main.layoutManager.overviewGroup.add_actor(view); @@ -699,7 +743,7 @@ var WorkspacesDisplay = { } }, - _onScrollEvent: function(actor, event) { + _onScrollEvent(actor, event) { if (this._swipeTracker.canHandleScrollEvent(event)) return Clutter.EVENT_PROPAGATE; @@ -707,35 +751,37 @@ var WorkspacesDisplay = { return Clutter.EVENT_PROPAGATE; if (this._workspacesOnlyOnPrimary && - this._getMonitorIndexForEvent(event) != this._primaryIndex) + this._getMonitorIndexForEvent(event) !== this._primaryIndex) return Clutter.EVENT_PROPAGATE; - const isShiftPressed = (event.get_state() & Clutter.ModifierType.SHIFT_MASK) != 0; - /*const isCtrlPressed = (event.get_state() & Clutter.ModifierType.CONTROL_MASK) != 0; - const isAltPressed = (event.get_state() & Clutter.ModifierType.MOD1_MASK) != 0; - const noModifiersPressed = !(isCtrlPressed && isShiftPressed && isAltPressed); - - if (OVERVIEW_MODE2 && noModifiersPressed) { - Main.overview.hide(); - return Clutter.EVENT_STOP; - }*/ + if (opt.PANEL_MODE === 1) { + const panelBox = Main.layoutManager.panelBox; + const [, y] = global.get_pointer(); + if (y > panelBox.allocation.y1 && y < panelBox.allocation.y2) + return Clutter.EVENT_STOP; + } - let direction = event.get_scroll_direction(); + if (_Util.isShiftPressed()) { + let direction = _Util.getScrollDirection(event); + if (direction === null || (Date.now() - this._lastScrollTime) < 150) + return Clutter.EVENT_STOP; + this._lastScrollTime = Date.now(); - if (/*SHIFT_REORDERS_WS && */isShiftPressed) { - if (direction === Clutter.ScrollDirection.UP) { + if (direction === Clutter.ScrollDirection.UP) direction = -1; - } - else if (direction === Clutter.ScrollDirection.DOWN) { + + else if (direction === Clutter.ScrollDirection.DOWN) direction = 1; - } else { + else direction = 0; - } + if (direction) { - _reorderWorkspace(direction); + _Util.reorderWorkspace(direction); // make all workspaces on primary monitor visible for case the new position is hidden - Main.overview._overview._controls._workspacesDisplay._workspacesViews[0]._workspaces.forEach(w => w.visible = true); + Main.overview._overview._controls._workspacesDisplay._workspacesViews[0]._workspaces.forEach(w => { + w.visible = true; + }); return Clutter.EVENT_STOP; } } @@ -743,24 +789,30 @@ var WorkspacesDisplay = { return Main.wm.handleWorkspaceScroll(event); }, - _onKeyPressEvent: function(actor, event) { + _onKeyPressEvent(actor, event) { const symbol = event.get_key_symbol(); - /*const { ControlsState } = OverviewControls; + /* const { ControlsState } = OverviewControls; if (this._overviewAdjustment.value !== ControlsState.WINDOW_PICKER && symbol !== Clutter.KEY_space) return Clutter.EVENT_PROPAGATE;*/ - /*if (!this.reactive) - return Clutter.EVENT_PROPAGATE;**/ - const isCtrlPressed = (event.get_state() & Clutter.ModifierType.CONTROL_MASK) != 0; - const isShiftPressed = (event.get_state() & Clutter.ModifierType.SHIFT_MASK) != 0; - const isAltPressed = (event.get_state() & Clutter.ModifierType.MOD1_MASK) != 0; + /* if (!this.reactive) + return Clutter.EVENT_PROPAGATE; */ const { workspaceManager } = global; const vertical = workspaceManager.layout_rows === -1; const rtl = this.get_text_direction() === Clutter.TextDirection.RTL; + const state = this._overviewAdjustment.value; let which; switch (symbol) { - /*case Clutter.KEY_Return:*/ + case Clutter.KEY_Return: + case Clutter.KEY_KP_Enter: + if (_Util.isCtrlPressed()) { + Main.ctrlAltTabManager._items.forEach(i => { + if (i.sortGroup === 1 && i.name === 'Dash') + Main.ctrlAltTabManager.focusGroup(i); + }); + } + return Clutter.EVENT_STOP; case Clutter.KEY_Page_Up: if (vertical) which = Meta.MotionDirection.UP; @@ -784,30 +836,57 @@ var WorkspacesDisplay = { which = workspaceManager.n_workspaces - 1; break; case Clutter.KEY_space: - if (isCtrlPressed && isShiftPressed) { + if (_Util.isCtrlPressed() && _Util.isShiftPressed()) { _Util.openPreferences(); - } else if (isAltPressed) { - Main.ctrlAltTabManager._items.forEach(i => {if (i.sortGroup === 1 && i.name === 'Dash') Main.ctrlAltTabManager.focusGroup(i)}); - } else if (opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED && isCtrlPressed) { + } else if (_Util.isAltPressed()) { + Main.ctrlAltTabManager._items.forEach(i => { + if (i.sortGroup === 1 && i.name === 'Dash') + Main.ctrlAltTabManager.focusGroup(i); + }); + } else if (opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED && _Util.isCtrlPressed()) { _Util.activateSearchProvider(SEARCH_RECENT_FILES_PREFIX); - } else if (opt.WINDOW_SEARCH_PROVIDER_ENABLED/* && SEARCH_WINDOWS_SPACE*/) { + } else if (opt.WINDOW_SEARCH_PROVIDER_ENABLED) { _Util.activateSearchProvider(SEARCH_WINDOWS_PREFIX); } + return Clutter.EVENT_STOP; case Clutter.KEY_Down: case Clutter.KEY_Left: case Clutter.KEY_Right: case Clutter.KEY_Up: + case Clutter.KEY_Tab: if (Main.overview._overview._controls._searchController.searchActive) { Main.overview.searchEntry.grab_key_focus(); - } else /*if (OVERVIEW_MODE && !WORKSPACE_MODE)*/ { - Main.ctrlAltTabManager._items.forEach(i => {if (i.sortGroup === 1 && i.name === 'Dash') Main.ctrlAltTabManager.focusGroup(i)}); + } else if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE && state === 1) { + // expose windows by "clicking" on ws thumbnail + // in this case overview stateAdjustment will be used for transition + Main.overview._overview.controls._thumbnailsBox._activateThumbnailAtPoint(0, 0, global.get_current_time(), true); + Main.ctrlAltTabManager._items.forEach(i => { + if (i.sortGroup === 1 && i.name === 'Windows') + Main.ctrlAltTabManager.focusGroup(i); + }); + } else if (opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE && state === 1) { + // expose windows for OVERVIEW_MODE 1 + const adjustment = this._workspacesViews[0]._workspaces[global.workspace_manager.get_active_workspace().index()]._background._stateAdjustment; + opt.WORKSPACE_MODE = 1; + _Util.exposeWindows(adjustment, true); + } else { + if (state === 2) + return Clutter.EVENT_PROPAGATE; + Main.ctrlAltTabManager._items.forEach(i => { + if (i.sortGroup === 1 && i.name === 'Windows') + Main.ctrlAltTabManager.focusGroup(i); + }); } + return Clutter.EVENT_STOP; default: return Clutter.EVENT_PROPAGATE; } + if (state === 2) + return Clutter.EVENT_PROPAGATE; + let ws; if (which < 0) // Negative workspace numbers are directions @@ -816,17 +895,19 @@ var WorkspacesDisplay = { // Otherwise it is a workspace index ws = workspaceManager.get_workspace_by_index(which); - if (/*SHIFT_REORDERS_WS && */isShiftPressed) { + if (_Util.isShiftPressed()) { let direction; if (which === Meta.MotionDirection.UP || which === Meta.MotionDirection.LEFT) direction = -1; else if (which === Meta.MotionDirection.DOWN || which === Meta.MotionDirection.RIGHT) direction = 1; if (direction) - _reorderWorkspace(direction); + _Util.reorderWorkspace(direction); // make all workspaces on primary monitor visible for case the new position is hidden - Main.overview._overview._controls._workspacesDisplay._workspacesViews[0]._workspaces.forEach(w => w.visible = true); - return Clutter.EVENT_STOP; + Main.overview._overview._controls._workspacesDisplay._workspacesViews[0]._workspaces.forEach(w => { + w.visible = true; + }); + return Clutter.EVENT_STOP; } if (ws) @@ -834,7 +915,7 @@ var WorkspacesDisplay = { return Clutter.EVENT_STOP; }, -} +}; // same copy of this function should be available in OverviewControls and WorkspacesView function _getFitModeForState(state) { @@ -851,14 +932,3 @@ function _getFitModeForState(state) { return FitMode.SINGLE; } } - -// ------------------ Reorder Workspaces - callback for Dash and workspacesDisplay ----------------------------------- - -function _reorderWorkspace(direction = 0) { - let activeWs = global.workspace_manager.get_active_workspace(); - let activeWsIdx = activeWs.index(); - let targetIdx = activeWsIdx + direction; - if (targetIdx > -1 && targetIdx < (global.workspace_manager.get_n_workspaces())) { - global.workspace_manager.reorder_workspace(activeWs, targetIdx); - } -} |