diff options
Diffstat (limited to 'extensions/47/vertical-workspaces/lib/overviewControls.js')
-rw-r--r-- | extensions/47/vertical-workspaces/lib/overviewControls.js | 1633 |
1 files changed, 1633 insertions, 0 deletions
diff --git a/extensions/47/vertical-workspaces/lib/overviewControls.js b/extensions/47/vertical-workspaces/lib/overviewControls.js new file mode 100644 index 0000000..c5a74f1 --- /dev/null +++ b/extensions/47/vertical-workspaces/lib/overviewControls.js @@ -0,0 +1,1633 @@ +/** + * V-Shell (Vertical Workspaces) + * overviewControls.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2024 + * @license GPL-3.0 + * + */ + +'use strict'; + +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; +import Shell from 'gi://Shell'; + +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import * as Overview from 'resource:///org/gnome/shell/ui/overview.js'; +import * as OverviewControls from 'resource:///org/gnome/shell/ui/overviewControls.js'; +import * as WorkspacesView from 'resource:///org/gnome/shell/ui/workspacesView.js'; +import * as Background from 'resource:///org/gnome/shell/ui/background.js'; +import * as Util from 'resource:///org/gnome/shell/misc/util.js'; + +let Me; +let opt; +// gettext +let _; + +const ControlsState = OverviewControls.ControlsState; +const FitMode = WorkspacesView.FitMode; + +const STARTUP_ANIMATION_TIME = 500; +const ANIMATION_TIME = Overview.ANIMATION_TIME; +const DASH_MAX_SIZE_RATIO = 0.35; + +let _timeouts; + +export const OverviewControlsModule = class { + constructor(me) { + Me = me; + opt = Me.opt; + _ = Me.gettext; + + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + } + + cleanGlobals() { + Me = null; + opt = null; + _ = null; + } + + update(reset) { + this._removeTimeouts(); + this.moduleEnabled = true; + const conflict = false; + + reset = reset || !this.moduleEnabled || conflict; + + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' OverviewControlsModule - Keeping untouched'); + } + + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); + + _timeouts = {}; + + this._replaceOnSearchChanged(); + + this._overrides.addOverride('ControlsManager', OverviewControls.ControlsManager.prototype, ControlsManagerCommon); + this._overrides.addOverride('ControlsManagerLayoutCommon', Main.overview._overview.controls.layoutManager, ControlsManagerLayoutCommon); + if (opt.ORIENTATION === Clutter.Orientation.VERTICAL) + this._overrides.addOverride('ControlsManagerLayout', Main.overview._overview.controls.layoutManager, ControlsManagerLayoutVertical); + else + this._overrides.addOverride('ControlsManagerLayout', Main.overview._overview.controls.layoutManager, ControlsManagerLayoutHorizontal); + + // Allow user to close the overview by clicking on an empty space on the primary monitor's overview + // Secondary monitors are handled in workspacesView + this._addClickToCloseOverview(); + + // Update custom workAreaBox + Main.overview._overview.controls.layoutManager._updateWorkAreaBox(); + + console.debug(' OverviewControlsModule - Activated'); + } + + _disableModule() { + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; + + const reset = true; + this._replaceOnSearchChanged(reset); + Main.overview._overview._controls._appDisplay.opacity = 255; + this._addClickToCloseOverview(reset); + + console.debug(' OverviewControlsModule - Disabled'); + } + + _removeTimeouts() { + if (_timeouts) { + Object.values(_timeouts).forEach(t => { + if (t) + GLib.source_remove(t); + }); + _timeouts = null; + } + } + + _replaceOnSearchChanged(reset) { + const searchController = Main.overview.searchController; + if (reset) { + if (this._searchControllerSigId) { + searchController.disconnect(this._searchControllerSigId); + this._searchControllerSigId = 0; + } + if (this._originalSearchControllerSigId) { + searchController.unblock_signal_handler(this._originalSearchControllerSigId); + this._originalSearchControllerSigId = 0; + } + searchController._searchResults.translation_x = 0; + 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) + if (!this._originalSearchControllerSigId) { + this._originalSearchControllerSigId = GObject.signal_handler_find(searchController, { signalId: 'notify', detail: 'search-active' }); + if (this._originalSearchControllerSigId) + searchController.block_signal_handler(this._originalSearchControllerSigId); + } + + if (!this._searchControllerSigId) + this._searchControllerSigId = searchController.connect('notify::search-active', () => Main.overview._overview.controls._onSearchChanged()); + } + } + + _addClickToCloseOverview(reset) { + const overview = Main.overview._overview; + + overview.reactive = false; + if (this._clickEmptyConId) { + overview.disconnect(this._clickEmptyConId); + this._clickEmptyConId = 0; + } + + if (reset || !opt.CLICK_EMPTY_CLOSE) + return; + + overview.reactive = true; + this._clickEmptyConId = overview.connect('button-release-event', (actor, event) => { + const button = event.get_button(); + const overviewState = overview.controls._stateAdjustment.value; + const buttonPrimary = button === Clutter.BUTTON_PRIMARY; + const buttonSecondary = button === Clutter.BUTTON_SECONDARY; + const buttonAny = buttonPrimary || buttonSecondary; + + if ((overviewState === 1 && buttonAny) || (overviewState === 2 && buttonSecondary)) + Main.overview.hide(); + }); + } +}; + +const ControlsManagerCommon = { + // 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() { + ... + }*/ + + prepareToEnterOverview() { + this._searchController.prepareToEnterOverview(); + this._workspacesDisplay.prepareToEnterOverview(); + // Workaround for thumbnailsBox not re-scaling after switching workspace outside of overview using a trackpad + this._thumbnailsBox._updateIndicator(); + + Main.overview._overview.controls.opacity = 255; + + // Ensure that overview backgrounds are ready when needed + if (!this._bgManagers && (opt.SHOW_BG_IN_OVERVIEW || !opt.SHOW_WS_PREVIEW_BG)) + this._setBackground(); + else if (this._bgManagers && !(opt.SHOW_BG_IN_OVERVIEW || !opt.SHOW_WS_PREVIEW_BG)) + this._setBackground(true); + + // store pointer X coordinate for OVERVIEW_MODE 1 - to prevent immediate switch to WORKSPACE_MODE 1 if the mouse pointer is steady + opt.showingPointerX = global.get_pointer()[0]; + }, + + // this function has duplicate in WorkspaceView so we use one function for both to avoid issues with syncing them + _getFitModeForState(state) { + return _getFitModeForState(state); + }, + + _updateThumbnailsBox() { + const { shouldShow } = this._thumbnailsBox; + const thumbnailsBoxVisible = shouldShow; + this._thumbnailsBox.visible = thumbnailsBoxVisible; + + // this call should be directly in _update(), but it's used as a callback function and it would require to reconnect the signal + this._updateOverview(); + }, + + // this function is pure addition to the original code and handles wsDisp transition to APP_GRID view + _updateOverview() { + 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 paramsForState = s => { + let opacity; + switch (s) { + case ControlsState.HIDDEN: + case ControlsState.WINDOW_PICKER: + opacity = 255; + break; + case ControlsState.APP_GRID: + opacity = 0; + break; + default: + opacity = 255; + break; + } + return { opacity }; + }; + + let initialParams = paramsForState(initialState); + let finalParams = paramsForState(finalState); + + let opacity = Math.round(Util.lerp(initialParams.opacity, finalParams.opacity, progress)); + + let workspacesDisplayVisible = opacity !== 0; + + // improve transition from search results to desktop + if (finalState === 0 && this._searchController._searchResults.visible) + this._searchController.hide(); + + // reset Static Workspace window picker mode + if (currentState === 0 && opt.OVERVIEW_MODE && opt.WORKSPACE_MODE) + opt.WORKSPACE_MODE = 0; + + if (!opt.WS_ANIMATION || (!opt.SHOW_WS_TMB && opt.SHOW_WS_PREVIEW_BG)) { + this._workspacesDisplay.opacity = opacity; + } else if (!opt.SHOW_WS_TMB_BG && opt.SHOW_WS_PREVIEW_BG) { + // fade out ws wallpaper during transition to ws switcher if ws switcher background disabled + const workspaces = this._workspacesDisplay._workspacesViews[global.display.get_primary_monitor()]?._workspaces; + // Speed up the workspace background opacity transition + if (opt.WORKSPACE_MAX_SPACING < opt.WS_MAX_SPACING_OFF_SCREEN && workspaces) + // If workspacesDisplay max spacing is set so adjacent workspaces could be visible on the screen + workspaces.forEach(w => w._background.set_opacity(Math.max(0, opacity - (255 - opacity)))); + else if (workspaces) + // If adjacent workspaces should not be visible on the screen, set the opacity only for the visible one + workspaces[this._workspaceAdjustment.value]?._background.set_opacity(Math.max(0, opacity - (255 - opacity))); + } + + // if ws preview background is disabled, animate tmb box and dash + const tmbBox = this._thumbnailsBox; + 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 = Me.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 (!tmbBox._translationOriginal || Math.abs(tmbBox._translationOriginal[0]) > 500) { // swipe gesture can call this calculation before tmbBox is realized, giving nonsense width + const [dashTranslationX, dashTranslationY, tmbTranslationX, tmbTranslationY, searchTranslationY] = this._getOverviewTranslations(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); + tmbBox.translation_x = Math.round(prg * tmbBox._translationOriginal[0]); + tmbBox.translation_y = Math.round(prg * tmbBox._translationOriginal[1]); + if (!skipDash) { + dash.translation_x = Math.round(prg * dash._translationOriginal[0]); + dash.translation_y = Math.round(prg * dash._translationOriginal[1]); + } + searchEntryBin.translation_y = Math.round(prg * searchEntryBin._translationOriginal); + } + if (progress === 1) { + tmbBox._translationOriginal = 0; + if (!skipDash) + dash._translationOriginal = 0; + + searchEntryBin._translationOriginal = 0; + } + } else if (!Main.layoutManager._startingUp && (tmbBox.translation_x || tmbBox.translation_y)) { + tmbBox.translation_x = 0; + tmbBox.translation_y = 0; + if (!skipDash) { + dash.translation_x = 0; + dash.translation_y = 0; + } + searchEntryBin.translation_y = 0; + } + + if (!Main.layoutManager._startingUp) { + if (initialState === ControlsState.HIDDEN && finalState === ControlsState.APP_GRID) + this._appDisplay.opacity = Math.round(progress * 255); + else + this._appDisplay.opacity = 255 - opacity; + } + + if (currentState === ControlsState.APP_GRID) { + // in app grid hide workspaces so they're not blocking app grid or ws thumbnails + this._workspacesDisplay.scale_x = 0; + } else { + this._workspacesDisplay.scale_x = 1; + } + if (opt.LEAVING_SEARCH && currentState <= ControlsState.WINDOW_PICKER) { + opt.LEAVING_SEARCH = false; + } + + this._workspacesDisplay.setPrimaryWorkspaceVisible(workspacesDisplayVisible); + + if (!this.dash._isAbove && progress > 0 && opt.OVERVIEW_MODE2) { + // 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 + if (!Me.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 + this.set_child_above_sibling(this._thumbnailsBox, null); + this.set_child_above_sibling(this._searchEntryBin, null); + if (!Me.Util.dashNotDefault()) + this.set_child_above_sibling(this.dash, null); + if (Main.layoutManager.panelBox.get_parent() === Main.layoutManager.overviewGroup) + Main.layoutManager.overviewGroup.set_child_above_sibling(Main.layoutManager.panelBox, Main.overview._overview); + 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); + if (Main.layoutManager.panelBox.get_parent() === Main.layoutManager.overviewGroup) + Main.layoutManager.overviewGroup.set_child_below_sibling(Main.layoutManager.panelBox, Main.overview._overview); + this.dash._isAbove = false; + } + }, + + // fix for upstream bug - appGrid.visible after transition from APP_GRID to HIDDEN + _updateAppDisplayVisibility(stateTransitionParams = null) { + if (!stateTransitionParams) + stateTransitionParams = this._stateAdjustment.getStateTransitionParams(); + + const { currentState } = stateTransitionParams; + if (this.dash.showAppsButton.checked) + this._searchTransition = 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 = + currentState > ControlsState.HIDDEN && + !this._searchController.searchActive && + !(currentState === ControlsState.WINDOW_PICKER && !opt.APP_GRID_ANIMATION) && + !this._searchTransition; + }, + + _activateSearchAppGridMode() { + if (!this._origAppGridContent) { + this._origAppGridContent = { + usage: opt.APP_GRID_USAGE, + favorites: opt.APP_GRID_EXCLUDE_FAVORITES, + running: opt.APP_GRID_EXCLUDE_RUNNING, + incompletePages: this._appDisplay._grid.layoutManager.allowIncompletePages, + order: opt.APP_GRID_ORDER, + }; + opt.APP_GRID_ORDER = 3; + opt.APP_GRID_USAGE = true; + opt.APP_GRID_EXCLUDE_FAVORITES = false; + opt.APP_GRID_EXCLUDE_RUNNING = false; + this._appDisplay._grid.layoutManager.allowIncompletePages = false; + this._appDisplay._redisplay(); + } + }, + + _deactivateSearchAppGridMode() { + if (this._origAppGridContent) { + const icons = this._appDisplay._orderedItems; + icons.forEach(icon => { + icon.visible = true; + }); + + opt.APP_GRID_ORDER = this._origAppGridContent.order; + opt.APP_GRID_USAGE = this._origAppGridContent.usage; + opt.APP_GRID_EXCLUDE_FAVORITES = this._origAppGridContent.favorites; + opt.APP_GRID_EXCLUDE_RUNNING = this._origAppGridContent.running; + this._appDisplay._grid.layoutManager.allowIncompletePages = this._origAppGridContent.incompletePages; + this._origAppGridContent = null; + this._appDisplay._redisplay(); + } + }, + + _onSearchChanged() { + // something is somewhere setting the opacity to 0 if V-Shell is rebased while in overview / search + this._searchController.opacity = 255; + + 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) { + if (!this.dash.showAppsButton.checked) + opt.LEAVING_SEARCH = true; + + if (this._origAppGridContent) + this._deactivateSearchAppGridMode(); + + this._workspacesDisplay.reactive = true; + this._workspacesDisplay.setPrimaryWorkspaceVisible(true); + } else { + if (opt.SEARCH_APP_GRID_MODE && this.dash.showAppsButton.checked) { + this._activateSearchAppGridMode(); + return; + } + + if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) + this._searchController._searchResults._statusText.add_style_class_name('search-statustext-om2'); + else + this._searchController._searchResults._statusText.remove_style_class_name('search-statustext-om2'); + this._searchController.show(); + entry.visible = true; + entry.opacity = 255; + opt.LEAVING_SEARCH = false; + } + + if (opt.SHOW_BG_IN_OVERVIEW && this._bgManagers) + this._updateBackground(this._bgManagers[0]); + this._searchTransition = true; + + this._searchController._searchResults.translation_x = 0; + this._searchController._searchResults.translation_y = 0; + this._searchController.visible = true; + + if (opt.SEARCH_VIEW_ANIMATION && ![4, 8].includes(opt.WS_TMB_POSITION)) { + this._updateAppDisplayVisibility(); + this._searchController._searchResults._statusBin.opacity = 1; + + let translationX = 0; + let translationY = 0; + const geometry = global.display.get_monitor_geometry(global.display.get_primary_monitor()); + + switch (opt.SEARCH_VIEW_ANIMATION) { + 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 = translationX; + this._searchController._searchResults.translation_y = translationY; + } else { + this._searchController._searchResults.translation_x = 0; + this._searchController._searchResults.translation_y = 0; + } + + this._searchController._searchResults.ease({ + delay: 150, // wait for results + opacity: searchActive ? 255 : 0, + 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._searchController._searchResults._statusBin.opacity = 255; + }, + }); + + this._workspacesDisplay.opacity = 255; + } else { + this._appDisplay.ease({ + opacity: searchActive || currentState < 2 ? 0 : 255, + duration: SIDE_CONTROLS_ANIMATION_TIME / 2, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + this._updateAppDisplayVisibility(); + }, + }); + + this._workspacesDisplay.setPrimaryWorkspaceVisible(true); + + this._searchController._searchResults.ease({ + opacity: searchActive ? 255 : 0, + 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 (!(opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) && !Main.overview._animationInProgress && finalState !== ControlsState.HIDDEN && !this.dash.showAppsButton.checked) { + this._searchController._searchResults._content.remove_style_class_name('search-section-content-bg-om2'); + this._searchEntry.remove_style_class_name('search-entry-om2'); + const duration = opt.SEARCH_VIEW_ANIMATION ? 140 : 0; + 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 ? duration : SIDE_CONTROLS_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + this._workspacesDisplay.setPrimaryWorkspaceVisible(!searchActive); + // Set the delay before processing a new search entry to 150 on deactivation, so search providers can't make make the workspace animation stuttering + // set it back to 0 after in-animation, so the search can be snappy + opt.SEARCH_DELAY = searchActive || !opt.SEARCH_VIEW_ANIMATION ? 0 : 150; + }, + }); + } 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 + this._searchController._searchResults._content.add_style_class_name('search-section-content-bg-om2'); + this._searchEntry.add_style_class_name('search-entry-om2'); + } else { + this._searchController._searchResults._content.remove_style_class_name('search-section-content-bg-om2'); + this._searchEntry.remove_style_class_name('search-entry-om2'); + } + }, + + async runStartupAnimation(callback) { + this._ignoreShowAppsButtonToggle = true; + + this.prepareToEnterOverview(); + + this._stateAdjustment.value = ControlsState.HIDDEN; + this._stateAdjustment.ease(ControlsState.WINDOW_PICKER, { + duration: ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + + this.dash.showAppsButton.checked = false; + this._ignoreShowAppsButtonToggle = false; + + // Set the opacity here to avoid a 1-frame flicker + this.opacity = 1; + this._appDisplay.opacity = 1; + + // We can't run the animation before the first allocation happens + await this.layout_manager.ensureAllocation(); + + this._setBackground(); + Me.Modules.panelModule.update(); + Main.panel.opacity = 255; + + // Opacity + this.ease({ + opacity: opt.STARTUP_STATE === 1 ? 0 : 255, + duration: STARTUP_ANIMATION_TIME, + mode: Clutter.AnimationMode.LINEAR, + }); + + const dash = this.dash; + const tmbBox = this._thumbnailsBox; + + // Set the opacity here to avoid a 1-frame flicker + dash.opacity = 0; + for (const view of this._workspacesDisplay._workspacesViews) { + if (view._monitorIndex !== global.display.get_primary_monitor()) + view._thumbnails.opacity = 0; + } + + const searchEntryBin = this._searchEntryBin; + const [dashTranslationX, dashTranslationY, tmbTranslationX, tmbTranslationY, searchTranslationY] = + this._getOverviewTranslations(dash, tmbBox, searchEntryBin); + + const onComplete = function () { + // running init callback again causes issues (multiple connections) + if (!Main.overview._startupInitComplete) + callback(); + + const appDisplayModule = Me.Modules.appDisplayModule; + if (!appDisplayModule.moduleEnabled) + this._finishStartupSequence(); + else + this._realizeAppDisplayAndFinishSequence(); + + Main.overview._startupInitComplete = true; + }.bind(this); + + if (dash.visible && !Me.Util.dashNotDefault()) { + dash.translation_x = dashTranslationX; + dash.translation_y = dashTranslationY; + dash.opacity = 255; + dash.ease({ + translation_x: 0, + translation_y: 0, + delay: STARTUP_ANIMATION_TIME / 2, + duration: STARTUP_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete, + }); + } else { + // 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 + _timeouts.startupAnim2 = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, + // delay + animation time + STARTUP_ANIMATION_TIME * 2 * St.Settings.get().slow_down_factor, + () => { + onComplete(); + _timeouts.startupAnim2 = 0; + return GLib.SOURCE_REMOVE; + } + ); + } + + if (searchEntryBin.visible) { + searchEntryBin.translation_y = searchTranslationY; + searchEntryBin.ease({ + translation_y: 0, + delay: STARTUP_ANIMATION_TIME / 2, + duration: STARTUP_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + + if (tmbBox.visible) { + tmbBox.translation_x = tmbTranslationX; + tmbBox.translation_y = tmbTranslationY; + tmbBox.ease({ + translation_x: 0, + translation_y: 0, + delay: STARTUP_ANIMATION_TIME / 2, + duration: STARTUP_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + + // upstream bug - following animation will be cancelled, don't know where + // needs further investigation + const workspacesViews = this._workspacesDisplay._workspacesViews; + if (workspacesViews.length > 1) { + for (const view of workspacesViews) { + if (view._monitorIndex !== global.display.get_primary_monitor() && view._thumbnails.visible) { + const secTmbBox = view._thumbnails; + + 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; + + secTmbBox.opacity = 255; + + secTmbBox.ease({ + translation_y: 0, + delay: STARTUP_ANIMATION_TIME / 2, + duration: STARTUP_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + } + } + }, + + _realizeAppDisplayAndFinishSequence() { + const appDisplayModule = Me.Modules.appDisplayModule; + // realize app grid for smoother first animation + appDisplayModule._repopulateAppDisplay(false, this._finishStartupSequence.bind(this)); + }, + + _finishStartupSequence() { + if (!this._bgManagers) + this._setBackground(); + + _timeouts.finishStartup = GLib.idle_add( + GLib.PRIORITY_LOW, () => { + this._appDisplay.opacity = 255; + if (opt.STARTUP_STATE === 1) { + Main.overview.hide(); + } else if (opt.STARTUP_STATE === 2) { + Main.overview.show(2); // just because of DtD, because we skipped startup animation + this.dash.showAppsButton.checked = true; + } else if (!opt.STARTUP_STATE && Me.Util.dashNotDefault()) { + Main.overview.show(); + } + + _timeouts.finishStartup = 0; + return GLib.SOURCE_REMOVE; + } + ); + }, + + setInitialTranslations() { + const dash = this.dash; + const tmbBox = this._thumbnailsBox; + const searchEntryBin = this._searchEntryBin; + const [dashTranslationX, dashTranslationY, tmbTranslationX, tmbTranslationY, searchTranslationY] = + this._getOverviewTranslations(dash, tmbBox, searchEntryBin); + if (!Me.Util.dashNotDefault()) { + dash.translation_x = dashTranslationX; + dash.translation_y = dashTranslationY; + } + tmbBox.translation_x = tmbTranslationX; + tmbBox.translation_y = tmbTranslationY; + searchEntryBin.translation_y = searchTranslationY; + }, + + _getOverviewTranslations(dash, tmbBox, searchEntryBin) { + const animationsDisabled = !St.Settings.get().enable_animations || ((opt.SHOW_WS_PREVIEW_BG && !opt.OVERVIEW_MODE2) && !Main.layoutManager._startingUp); + if (animationsDisabled) + return [0, 0, 0, 0, 0]; + + 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) { + const tmbWidth = tmbBox.width === Infinity ? 0 : tmbBox.width; + const tmbHeight = tmbBox.height === Infinity ? 0 : tmbBox.height; + switch (opt.WS_TMB_POSITION) { + case 3: // left + offset = 10 + (dash?.visible && opt.DASH_LEFT ? dash.width : 0); + tmbTranslationX = -tmbWidth - offset; + tmbTranslationY = 0; + break; + case 1: // right + offset = 10 + (dash?.visible && opt.DASH_RIGHT ? dash.width : 0); + tmbTranslationX = tmbWidth + offset; + tmbTranslationY = 0; + break; + case 0: // top + offset = 10 + (dash?.visible && opt.DASH_TOP ? dash.height : 0) + Main.panel.height; + tmbTranslationX = 0; + tmbTranslationY = -tmbHeight - 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 = tmbHeight + offset; + break; + } + } + + let dashTranslationX = 0; + let dashTranslationY = 0; + let position = opt.DASH_POSITION; + // if DtD replaced the original Dash, read its position + if (Me.Util.dashIsDashToDock()) + position = dash._position; + + if (dash?.visible) { + const dashWidth = dash.width === Infinity ? 0 : dash.width; + const dashHeight = dash.height === Infinity ? 0 : dash.height; + switch (position) { + case 0: // top + dashTranslationX = 0; + dashTranslationY = -dashHeight - dash.margin_bottom - Main.panel.height; + break; + case 1: // right + dashTranslationX = dashWidth; + dashTranslationY = 0; + break; + case 2: // bottom + dashTranslationX = 0; + dashTranslationY = dashHeight + dash.margin_bottom + Main.panel.height; + break; + case 3: // left + dashTranslationX = -dashWidth; + dashTranslationY = 0; + break; + } + } + + return [dashTranslationX, dashTranslationY, tmbTranslationX, tmbTranslationY, searchTranslationY]; + }, + + animateToOverview(state, callback) { + this._ignoreShowAppsButtonToggle = true; + this._searchTransition = false; + + this._stateAdjustment.value = ControlsState.HIDDEN; + + // building window thumbnails takes some time and with many windows on the workspace + // the time can be close to or longer than ANIMATION_TIME + // 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 + 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, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onStopped: () => { + if (callback) + callback(); + }, + }); + + this.dash.showAppsButton.checked = + state === ControlsState.APP_GRID; + + this._ignoreShowAppsButtonToggle = false; + }, + + _setBackground(reset = false) { + if (this._bgManagers) { + this._bgManagers.forEach(bg => { + this._stateAdjustment.disconnect(bg._fadeSignal); + bg.destroy(); + }); + } + + // if (!SHOW_BG_IN_OVERVIEW && !SHOW_WS_PREVIEW_BG) the background is used for static transition from wallpaper to empty bg in the overview + if (reset || (!opt.SHOW_BG_IN_OVERVIEW && opt.SHOW_WS_PREVIEW_BG)) { + delete this._bgManagers; + return; + } + + this._bgManagers = []; + for (const monitor of Main.layoutManager.monitors) { + const bgManager = new Background.BackgroundManager({ + monitorIndex: monitor.index, + container: Main.layoutManager.overviewGroup, + vignette: true, + }); + + bgManager.backgroundActor.content.vignette_sharpness = 0; + bgManager.backgroundActor.content.brightness = 1; + + bgManager._fadeSignal = this._stateAdjustment.connect('notify::value', v => { + this._updateBackground(bgManager, v.value, v); + }); + + if (monitor.index === global.display.get_primary_monitor()) { + bgManager._primary = true; + this._bgManagers.unshift(bgManager); // primary monitor first + } else { + bgManager._primary = false; + this._bgManagers.push(bgManager); + } + } + }, + + _updateBackground(bgManager, stateValue = 2, stateAdjustment = null) { + // Just in case something destroys our background (like older versions of Blur My Shell) + if (this._bgManagers[0] && !Main.layoutManager.overviewGroup.get_children().includes(this._bgManagers[0].backgroundActor)) { + console.error(`[${Me.metadata.name}]`, 'Error: The overview background has been destroyed, possibly by another incompatible extension'); + // remove and disconnect our destroyed backgrounds to avoid further errors + this._setBackground(true); + return; + } + + const finalState = stateAdjustment?.getStateTransitionParams().finalState; + if (!opt.SHOW_BG_IN_OVERVIEW && !opt.SHOW_WS_PREVIEW_BG) { + // if no bg shown in the overview, fade out the wallpaper + if (bgManager.backgroundActor.get_effect('blur')) + bgManager.backgroundActor.remove_effect_by_name('blur'); + if (!(opt.OVERVIEW_MODE2 && opt.WORKSPACE_MODE && finalState === 1)) + bgManager.backgroundActor.opacity = Util.lerp(255, 0, Math.min(stateValue, 1)); + } else { + bgManager.backgroundActor.opacity = 255; + let VIGNETTE, BRIGHTNESS, bgValue; + if (opt.OVERVIEW_MODE2 && stateValue <= 1 && !opt.WORKSPACE_MODE) { + VIGNETTE = 0; + BRIGHTNESS = 1; + bgValue = stateValue; + } else { + VIGNETTE = 0.2; + BRIGHTNESS = opt.OVERVIEW_BG_BRIGHTNESS; + if (opt.OVERVIEW_MODE2 && stateValue > 1 && !opt.WORKSPACE_MODE) + bgValue = stateValue - 1; + else + bgValue = stateValue; + } + + let blurEffect = bgManager.backgroundActor.get_effect('blur'); + if (!blurEffect) { + blurEffect = new Shell.BlurEffect({ + brightness: 1, + mode: Shell.BlurMode.ACTOR, + }); + bgManager.backgroundActor.add_effect_with_name('blur', blurEffect); + } + + // In GNOME 46 the "sigma" property has been renamed to "radius" + const radius = blurEffect.sigma !== undefined ? 'sigma' : 'radius'; + + const searchActive = this._searchController.searchActive; + if (searchActive) + BRIGHTNESS = opt.SEARCH_BG_BRIGHTNESS; + + bgManager.backgroundActor.content.vignette_sharpness = VIGNETTE; + bgManager.backgroundActor.content.brightness = BRIGHTNESS; + + let vignetteInit, brightnessInit;// , sigmaInit; + if (opt.SHOW_BG_IN_OVERVIEW && opt.SHOW_WS_PREVIEW_BG) { + vignetteInit = VIGNETTE; + brightnessInit = BRIGHTNESS; + // sigmaInit = opt.OVERVIEW_BG_BLUR_SIGMA; + } else { + vignetteInit = 0; + brightnessInit = 1; + // sigmaInit = 0; + } + + if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) { + bgManager.backgroundActor.content.vignette_sharpness = Util.lerp(vignetteInit, VIGNETTE, bgValue); + bgManager.backgroundActor.content.brightness = Util.lerp(brightnessInit, BRIGHTNESS, bgValue); + } else { + bgManager.backgroundActor.content.vignette_sharpness = Util.lerp(vignetteInit, VIGNETTE, Math.min(stateValue, 1)); + bgManager.backgroundActor.content.brightness = Util.lerp(brightnessInit, BRIGHTNESS, Math.min(stateValue, 1)); + } + + if (opt.OVERVIEW_BG_BLUR_SIGMA || opt.APP_GRID_BG_BLUR_SIGMA) { + // reduce number of steps of blur transition to improve performance + const step = opt.SMOOTH_BLUR_TRANSITIONS ? 0.05 : 0.2; + const progress = stateValue - (stateValue % step); + if (opt.SHOW_WS_PREVIEW_BG && stateValue < 1 && !searchActive) { // no need to animate transition, unless appGrid state is involved, static bg is covered by the ws preview bg + if (blurEffect[radius] !== opt.OVERVIEW_BG_BLUR_SIGMA) + blurEffect[radius] = opt.OVERVIEW_BG_BLUR_SIGMA; + } else if (stateValue < 1 && !searchActive && !(opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE)) { + const sigma = Math.round(Util.lerp(0, opt.OVERVIEW_BG_BLUR_SIGMA, progress)); + if (sigma !== blurEffect[radius]) + blurEffect[radius] = sigma; + } else if (stateValue < 1 && !searchActive && (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE && blurEffect[radius])) { + const sigma = Math.round(Util.lerp(0, opt.OVERVIEW_BG_BLUR_SIGMA, progress)); + if (sigma !== blurEffect[radius]) + blurEffect[radius] = sigma; + } else if (stateValue > 1 && !searchActive && (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE && finalState === 1)) { + const sigma = Math.round(Util.lerp(0, opt.OVERVIEW_BG_BLUR_SIGMA, progress % 1)); + if (sigma !== blurEffect[radius]) + blurEffect[radius] = sigma; + } else if ((stateValue > 1 && bgManager._primary) || searchActive) { + const sigma = Math.round(Util.lerp(opt.OVERVIEW_BG_BLUR_SIGMA, opt.APP_GRID_BG_BLUR_SIGMA, progress % 1)); + if (sigma !== blurEffect[radius]) + blurEffect[radius] = sigma; + } else if (stateValue === 1 && !(opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE)) { + blurEffect[radius] = opt.OVERVIEW_BG_BLUR_SIGMA; + } else if (stateValue === 0 || (stateValue === 1 && (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE))) { + blurEffect[radius] = 0; + } + } + } + }, +}; + +const ControlsManagerLayoutCommon = { + after__updateWorkAreaBox() { + const workArea = this._workAreaBox.copy(); + + // opt.PANEL_OVERVIEW_ONLY removes affectsStruts panel property + if (opt.get('panelModule') && opt.PANEL_OVERVIEW_ONLY) { + let offsetY = 0; + let reduction = 0; + reduction = Main.panel.height; + offsetY = opt.PANEL_POSITION_TOP ? reduction : 0; + + const startX = workArea.x1; + const startY = workArea.y1 + offsetY; + const width = workArea.get_width(); + const height = workArea.get_height() - reduction; + + workArea.set_origin(startX, startY); + workArea.set_size(width, height); + } + + this._workAreaBoxForVShellConfig = workArea; + }, + + _updatePositionFromDashToDock() { + // update variables that cannot be processed within settings + const dash = Main.overview.dash; + opt.DASH_POSITION = dash._position; + opt.DASH_TOP = opt.DASH_POSITION === 0; + opt.DASH_RIGHT = opt.DASH_POSITION === 1; + opt.DASH_BOTTOM = opt.DASH_POSITION === 2; + opt.DASH_LEFT = opt.DASH_POSITION === 3; + opt.DASH_VERTICAL = opt.DASH_LEFT || opt.DASH_RIGHT; + }, + + _dashToDockAffectsWorkArea() { + const dash = Main.overview.dash; + const dtd = dash.get_parent()?.get_parent()?.get_parent(); + const layoutManager = Main.layoutManager; + const index = layoutManager._findActor(dtd); + const data = index > -1 ? layoutManager._trackedActors[index] : null; + const affectsStruts = data?.affectsStruts; + return !!affectsStruts; + }, +}; + +const ControlsManagerLayoutVertical = { + _computeWorkspacesBoxForState(state, box, wsTmbWidth, wsTmbHeight, leftBoxOffset, rightBoxOffset, topBoxOffset, bottomBoxOffset, centeredBoxOffset) { + const workspaceBox = box.copy(); + let [width, height] = this._workAreaBoxForVShellConfig.get_size(); + const startX = this._workAreaBoxForVShellConfig.x1; + const startY = this._workAreaBoxForVShellConfig.y1; + + let wsBoxWidth, wsBoxHeight, wsBoxY, wsBoxX; + + switch (state) { + case ControlsState.HIDDEN: + workspaceBox.set_origin(...this._workAreaBox.get_origin()); + workspaceBox.set_size(...this._workAreaBox.get_size()); + break; + case ControlsState.WINDOW_PICKER: + case ControlsState.APP_GRID: + if (opt.WS_ANIMATION && opt.SHOW_WS_TMB && state === ControlsState.APP_GRID) { + workspaceBox.set_origin(...this._workspacesThumbnails.get_position()); + workspaceBox.set_size(wsTmbWidth, wsTmbHeight); + } else if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) { + workspaceBox.set_origin(...this._workAreaBox.get_origin()); + workspaceBox.set_size(...this._workAreaBox.get_size()); + } else { + wsBoxWidth = width - leftBoxOffset - rightBoxOffset; + wsBoxHeight = height - topBoxOffset - bottomBoxOffset; + + const ratio = width / height; + let wRatio = wsBoxWidth / wsBoxHeight; + let scale = ratio / wRatio; + + if (scale > 1) { + wsBoxHeight /= scale; + wsBoxWidth = wsBoxHeight * ratio; + } else { + wsBoxWidth *= scale; + wsBoxHeight = wsBoxWidth / ratio; + } + + // height decides the actual size, ratio is given by the workArea + wsBoxHeight = Math.round(wsBoxHeight * opt.WS_PREVIEW_SCALE); + wsBoxWidth = Math.round(wsBoxWidth * opt.WS_PREVIEW_SCALE); + + let xOffset = 0; + let yOffset = 0; + + const yAvailableSpace = Math.round((height - topBoxOffset - wsBoxHeight - bottomBoxOffset) / 2); + yOffset = topBoxOffset + yAvailableSpace; + + const centeredBoxX = Math.round((width - wsBoxWidth) / 2); + + this._xAlignCenter = false; + if (centeredBoxX < centeredBoxOffset) { + xOffset = Math.round(leftBoxOffset + (width - leftBoxOffset - wsBoxWidth - rightBoxOffset) / 2); + } else { + xOffset = centeredBoxX; + this._xAlignCenter = true; + } + + wsBoxX = startX + xOffset; + wsBoxY = startY + yOffset; + workspaceBox.set_origin(wsBoxX, wsBoxY); + workspaceBox.set_size(wsBoxWidth, wsBoxHeight); + } + } + + return workspaceBox; + }, + + _getAppDisplayBoxForState(state, box, leftBoxOffset, rightBoxOffset, topBoxOffset, bottomBoxOffset) { + const appDisplayBox = new Clutter.ActorBox(); + const startX = this._workAreaBoxForVShellConfig.x1; + const startY = this._workAreaBoxForVShellConfig.y1; + let [width, height] = this._workAreaBoxForVShellConfig.get_size(); + const centeredBoxOffset = Math.max(leftBoxOffset, rightBoxOffset); + + const adWidth = opt.CENTER_APP_GRID + ? width - 2 * centeredBoxOffset + : width - leftBoxOffset - rightBoxOffset; + const adHeight = height - topBoxOffset - bottomBoxOffset; + + const appDisplayX = startX + + (opt.CENTER_APP_GRID + ? Math.round((width - adWidth) / 2) + : leftBoxOffset + ); + const appDisplayY = startY + topBoxOffset; + + switch (state) { + case ControlsState.HIDDEN: + case ControlsState.WINDOW_PICKER: + // 1 - left, 2 - right, 3 - bottom, 5 - top + switch (opt.APP_GRID_ANIMATION) { + case 0: + appDisplayBox.set_origin(appDisplayX, appDisplayY); + break; + case 1: + appDisplayBox.set_origin(startX + width, appDisplayY); + break; + case 2: + appDisplayBox.set_origin(box.x1 - adWidth, appDisplayY); + break; + case 3: + appDisplayBox.set_origin(appDisplayX, box.y2); + break; + case 5: + appDisplayBox.set_origin(appDisplayX, box.y1 - adHeight); + break; + } + break; + case ControlsState.APP_GRID: + appDisplayBox.set_origin(appDisplayX, appDisplayY); + break; + } + + appDisplayBox.set_size(adWidth, adHeight); + return appDisplayBox; + }, + + vfunc_allocate(container, box) { + const childBox = new Clutter.ActorBox(); + const startX = this._workAreaBoxForVShellConfig.x1; + const startY = this._workAreaBoxForVShellConfig.y1; + let [width, height] = this._workAreaBoxForVShellConfig.get_size(); + + const transitionParams = this._stateAdjustment.getStateTransitionParams(); + const spacing = opt.SPACING; + + // Dash + const maxDashHeight = Math.round(box.get_height() * DASH_MAX_SIZE_RATIO); + const maxDashWidth = Math.round(maxDashHeight * 0.8); + let dashHeight = 0; + let dashWidth = 0; + + // dash cloud be overridden by the Dash to Dock clone + if (Me.Util.dashIsDashToDock()) { + this._updatePositionFromDashToDock(); + // If DtD affects workArea, dash size needs to be 0 + spacing + const dash = Main.overview.dash; + if (this._dashToDockAffectsWorkArea()) { + if (opt.DASH_VERTICAL) + dashWidth = spacing; + else + dashHeight = spacing; + } else { + dashHeight = dash.height; + dashWidth = dash.width; + if (opt.DASH_VERTICAL) + dashWidth += spacing; + else + dashHeight += spacing; + } + } else if (this._dash.visible) { + // default dock + if (opt.DASH_VERTICAL) { + this._dash.setMaxSize(maxDashWidth, height); + [, dashWidth] = this._dash.get_preferred_width(height); + [, 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); + [, dashHeight] = this._dash.get_preferred_height(width); + [, dashWidth] = this._dash.get_preferred_width(dashHeight); + dashHeight = Math.min(dashHeight, maxDashHeight); + dashWidth = Math.min(dashWidth, width); + } + } + + // Workspace Thumbnails + let wsTmbWidth = 0; + let wsTmbHeight = 0; + + if (opt.SHOW_WS_TMB) { + const searchActive = this._searchController.searchActive; + let maxWsTmbScale = this._dash.showAppsButton.checked && !(searchActive && !opt.SEARCH_APP_GRID_MODE) + ? opt.MAX_THUMBNAIL_SCALE_APPGRID + : opt.MAX_THUMBNAIL_SCALE; + if (transitionParams.currentState % 1 && !opt.MAX_THUMBNAIL_SCALE_STABLE && !searchActive && !opt.LEAVING_SEARCH) { + 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; + maxWsTmbScale = Util.lerp(initState, finalState, transitionParams.progress); + } + wsTmbWidth = Math.round(width * maxWsTmbScale); + + let totalTmbSpacing; + [totalTmbSpacing, wsTmbHeight] = this._workspacesThumbnails.get_preferred_height(wsTmbWidth); + wsTmbHeight += totalTmbSpacing; + + const wstTopOffset = !opt.WS_TMB_FULL && opt.DASH_TOP ? dashHeight : spacing; + const wstBottomOffset = !opt.WS_TMB_FULL && opt.DASH_BOTTOM ? dashHeight : spacing; + const wstLeftOffset = opt.DASH_LEFT ? dashWidth : spacing; + const wstRightOffset = opt.DASH_RIGHT ? dashWidth : spacing; + + const wsTmbHeightMax = height - wstTopOffset - wstBottomOffset; + + // Reduce size to fit wsTmb to the screen + if (wsTmbHeight > wsTmbHeightMax) { + wsTmbHeight = wsTmbHeightMax; + wsTmbWidth = this._workspacesThumbnails.get_preferred_width(wsTmbHeight)[1]; + } + + let wsTmbX = opt.WS_TMB_LEFT + ? startX + wstLeftOffset + : startX + width - wstRightOffset - wsTmbWidth; + + let offset = (height - wstTopOffset - wsTmbHeight - wstBottomOffset) / 2; + offset = Math.round(offset - (opt.WS_TMB_POSITION_ADJUSTMENT * offset)); + const wsTmbY = startY + wstTopOffset + offset; + + childBox.set_origin(wsTmbX, wsTmbY); + childBox.set_size(Math.max(wsTmbWidth, 1), Math.max(wsTmbHeight, 1)); + + this._workspacesThumbnails.allocate(childBox); + } + + if (this._dash.visible) { + const wMaxWidth = width - spacing - wsTmbWidth - 2 * spacing - (opt.DASH_VERTICAL ? dashWidth + spacing : 0); + if (opt.WS_TMB_FULL && !opt.DASH_VERTICAL) { + this._dash.setMaxSize(wMaxWidth, maxDashHeight); + [, dashHeight] = this._dash.get_preferred_height(wMaxWidth); + [, dashWidth] = this._dash.get_preferred_width(dashHeight); + dashHeight = Math.min(dashHeight, maxDashHeight); + dashWidth = Math.min(dashWidth, wMaxWidth); + } + + let dashX = opt.DASH_RIGHT ? width - dashWidth : 0; + let dashY = opt.DASH_TOP ? startY : startY + height - dashHeight; + + if (!opt.DASH_VERTICAL) { + const dashLeftOffset = (opt.WS_TMB_FULL || opt.CENTER_DASH_WS) && opt.WS_TMB_LEFT ? wsTmbWidth + spacing : 0; + const dashRightOffset = (opt.WS_TMB_FULL || opt.CENTER_DASH_WS) && opt.WS_TMB_RIGHT ? wsTmbWidth + spacing : 0; + let offset = (width - dashWidth - (opt.CENTER_DASH_WS && !this._xAlignCenter ? dashLeftOffset + dashRightOffset : 0)) / 2; + offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - spacing); + dashX = startX + (opt.CENTER_DASH_WS ? dashLeftOffset : 0) + offset; + if (opt.WS_TMB_FULL) // Limit the adjustment while keeping the center of adjustment on the screen center + dashX = Math.clamp(startX + dashLeftOffset + spacing, dashX, startX + width - dashRightOffset - spacing - dashWidth); + } else { + const offset = (height - dashHeight) / 2; + dashY = startY + (offset - opt.DASH_POSITION_ADJUSTMENT * (offset - spacing)); + } + dashY = Math.round(dashY); + + childBox.set_origin(startX + dashX, dashY); + childBox.set_size(dashWidth, dashHeight); + this._dash.allocate(childBox); + } + + // View box offsets + const leftBoxOffset = (opt.DASH_LEFT ? dashWidth : spacing) + (opt.WS_TMB_LEFT ? wsTmbWidth + spacing : 0); + const rightBoxOffset = (opt.DASH_RIGHT ? dashWidth : spacing) + (opt.WS_TMB_RIGHT ? wsTmbWidth + spacing : 0); + let topBoxOffset = (opt.DASH_TOP ? dashHeight : spacing) + (opt.WS_TMB_TOP ? wsTmbHeight + spacing : 0); + const bottomBoxOffset = (opt.DASH_BOTTOM ? dashHeight : spacing) + (opt.WS_TMB_BOTTOM ? wsTmbHeight + spacing : 0); + const centeredBoxOffset = Math.max(leftBoxOffset, rightBoxOffset); + + // App grid needs to be calculated for the max wsTmbWidth in app grid, independently on the current wsTmb scale + const wsTmbWidthAppGrid = Math.round(width * opt.MAX_THUMBNAIL_SCALE_APPGRID); + const leftBoxOffsetAppGrid = (opt.DASH_LEFT ? dashWidth : spacing) + (opt.WS_TMB_LEFT ? wsTmbWidthAppGrid + spacing : 0); + const rightBoxOffsetAppGrid = (opt.DASH_RIGHT ? dashWidth : spacing) + (opt.WS_TMB_RIGHT ? wsTmbWidthAppGrid + spacing : 0); + + // searchEntry + const [searchEntryHeight] = this._searchEntry.get_preferred_height(width - wsTmbWidth); + const searchEntryY = startY + topBoxOffset; + + const searchX = startX + + (opt.CENTER_SEARCH_VIEW || this._xAlignCenter + ? centeredBoxOffset + : leftBoxOffset); // xAlignCenter is set by wsBox + + const searchWidth = + width - (opt.CENTER_SEARCH_VIEW || this._xAlignCenter + ? 2 * centeredBoxOffset + : leftBoxOffset + rightBoxOffset); + + childBox.set_origin(searchX, searchEntryY); + childBox.set_size(searchWidth, searchEntryHeight); + + this._searchEntry.allocate(childBox); + + // searchResults + const searchY = startY + topBoxOffset + searchEntryHeight + spacing; + const searchHeight = height - topBoxOffset - bottomBoxOffset - searchEntryHeight - 2 * spacing; + + childBox.set_origin(searchX, searchY); + childBox.set_size(searchWidth, searchHeight); + this._searchController.allocate(childBox); + + // Add searchEntry height if needed + topBoxOffset += opt.SHOW_SEARCH_ENTRY ? searchEntryHeight + spacing : 0; + + // workspace + let params = [box, wsTmbWidth, wsTmbHeight, leftBoxOffset, rightBoxOffset, topBoxOffset, bottomBoxOffset, centeredBoxOffset]; + + // Update cached boxes + for (const state of Object.values(ControlsState)) { + this._cachedWorkspaceBoxes.set( + state, this._computeWorkspacesBoxForState(state, ...params)); + } + + let workspacesBox; + 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); + workspacesBox = initialBox.interpolate(finalBox, transitionParams.progress); + } + + this._workspacesDisplay.allocate(workspacesBox); + + // appDisplay + params = [ + box, + leftBoxOffsetAppGrid, + rightBoxOffsetAppGrid, + topBoxOffset, + bottomBoxOffset, + ]; + let appDisplayBox; + if (!transitionParams.transitioning) { + appDisplayBox = + this._getAppDisplayBoxForState(transitionParams.currentState, ...params); + } else { + const initialBox = + this._getAppDisplayBoxForState(transitionParams.initialState, ...params); + const finalBox = + this._getAppDisplayBoxForState(transitionParams.finalState, ...params); + + appDisplayBox = initialBox.interpolate(finalBox, transitionParams.progress); + } + this._appDisplay.allocate(appDisplayBox); + + this._runPostAllocation(); + }, +}; + +const ControlsManagerLayoutHorizontal = { + _computeWorkspacesBoxForState: ControlsManagerLayoutVertical._computeWorkspacesBoxForState, + + _getAppDisplayBoxForState: ControlsManagerLayoutVertical._getAppDisplayBoxForState, + + vfunc_allocate(container, box) { + const childBox = new Clutter.ActorBox(); + const startX = this._workAreaBoxForVShellConfig.x1; + const startY = this._workAreaBoxForVShellConfig.y1; + let [width, height] = this._workAreaBoxForVShellConfig.get_size(); + + const transitionParams = this._stateAdjustment.getStateTransitionParams(); + const spacing = opt.SPACING; + + // Dash + const maxDashHeight = Math.round(box.get_height() * DASH_MAX_SIZE_RATIO); + const maxDashWidth = Math.round(maxDashHeight * 0.8); + let dashHeight = 0; + let dashWidth = 0; + + // dash cloud be overridden by the Dash to Dock clone + if (Me.Util.dashIsDashToDock()) { + this._updatePositionFromDashToDock(); + // If DtD affects workArea, dash size needs to be 0 + const dash = Main.overview.dash; + if (this._dashToDockAffectsWorkArea()) { + if (opt.DASH_VERTICAL) + dashWidth = spacing; + else + dashHeight = spacing; + } else { + dashHeight = dash.height; + dashWidth = dash.width; + if (opt.DASH_VERTICAL) + dashWidth += spacing; + else + dashHeight += spacing; + } + } else if (this._dash.visible) { + // default dock + if (!opt.DASH_VERTICAL) { + this._dash.setMaxSize(width, maxDashHeight); + [, dashHeight] = this._dash.get_preferred_height(width); + [, 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); + [, dashHeight] = this._dash.get_preferred_height(dashWidth); + dashHeight = Math.min(dashHeight, height - spacing); + dashWidth = Math.min(dashWidth, width); + } + } + + const [searchEntryHeight] = this._searchEntry.get_preferred_height(width); + + // Workspace Thumbnails + let wsTmbWidth = 0; + let wsTmbHeight = 0; + + if (opt.SHOW_WS_TMB) { + const searchActive = this._searchController.searchActive; + let maxWsTmbScale = this._dash.showAppsButton.checked && !(searchActive && !opt.SEARCH_APP_GRID_MODE) + ? opt.MAX_THUMBNAIL_SCALE_APPGRID + : opt.MAX_THUMBNAIL_SCALE; + if (transitionParams.currentState % 1 && !opt.MAX_THUMBNAIL_SCALE_STABLE && !searchActive && !opt.LEAVING_SEARCH) { + 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; + maxWsTmbScale = Util.lerp(initState, finalState, transitionParams.progress); + } + + wsTmbHeight = Math.round(height * maxWsTmbScale); + + let totalTmbSpacing; + [totalTmbSpacing, wsTmbWidth] = this._workspacesThumbnails.get_preferred_width(wsTmbHeight); + wsTmbWidth += totalTmbSpacing; + + const wstLeftOffset = !opt.WS_TMB_FULL && opt.DASH_LEFT ? dashWidth : spacing; + const wstRightOffset = !opt.WS_TMB_FULL && opt.DASH_RIGHT ? dashWidth : spacing; + const wstTopOffset = opt.DASH_TOP ? dashHeight : spacing; + const wstBottomOffset = opt.DASH_BOTTOM ? dashHeight : spacing; + + const wsTmbWidthMax = width - wstLeftOffset - wstRightOffset; + // Reduce size to fit wsTmb to the screen + if (wsTmbWidth > wsTmbWidthMax) { + wsTmbWidth = wsTmbWidthMax; + wsTmbHeight = this._workspacesThumbnails.get_preferred_height(wsTmbWidth)[1]; + } + + let wsTmbY = opt.WS_TMB_TOP + ? startY + wstTopOffset + : startY + height - wstBottomOffset - wsTmbHeight; + + let offset = (width - wstLeftOffset - wsTmbWidth - wstRightOffset) / 2; + offset = Math.round(offset - (opt.WS_TMB_POSITION_ADJUSTMENT * offset)); + const wsTmbX = startX + wstLeftOffset + offset; + + childBox.set_origin(wsTmbX, wsTmbY); + childBox.set_size(Math.max(wsTmbWidth, 1), Math.max(wsTmbHeight, 1)); + this._workspacesThumbnails.allocate(childBox); + } + + if (this._dash.visible) { + if (opt.WS_TMB_FULL && opt.DASH_VERTICAL) { + const wMaxHeight = height - spacing - wsTmbHeight; + this._dash.setMaxSize(maxDashWidth, wMaxHeight); + [, dashWidth] = this._dash.get_preferred_width(wMaxHeight); + [, dashHeight] = this._dash.get_preferred_height(dashWidth); + dashWidth = Math.min(dashWidth, maxDashWidth); + dashHeight = Math.min(dashHeight, wMaxHeight); + } + + let dashX = opt.DASH_RIGHT ? width - dashWidth : 0; + let dashY = opt.DASH_TOP ? startY : startY + height - dashHeight; + + if (opt.DASH_VERTICAL) { + const dashTopOffset = (opt.WS_TMB_FULL || opt.CENTER_DASH_WS) && opt.WS_TMB_TOP ? wsTmbHeight + spacing : 0; + const dashBottomOffset = (opt.WS_TMB_FULL || opt.CENTER_DASH_WS) && opt.WS_TMB_BOTTOM ? wsTmbHeight + spacing : 0; + let offset = (height - dashHeight - (opt.CENTER_DASH_WS ? dashTopOffset + dashBottomOffset : 0)) / 2; + offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - spacing); + dashY = startY + (opt.CENTER_DASH_WS ? dashTopOffset : 0) + offset; + if (opt.WS_TMB_FULL) // Limit the adjustment while keeping the center of adjustment on the screen center + dashY = Math.clamp(startY + dashTopOffset + spacing, dashY, startY + height - dashBottomOffset - spacing - dashHeight); + } else { + const offset = (width - dashWidth) / 2; + dashX = startX + (offset - opt.DASH_POSITION_ADJUSTMENT * (offset - spacing)); + } + dashX = Math.round(dashX); + + childBox.set_origin(startX + dashX, dashY); + childBox.set_size(dashWidth, dashHeight); + this._dash.allocate(childBox); + } + + // Main view offsets + const leftBoxOffset = opt.DASH_LEFT ? dashWidth : spacing; + const rightBoxOffset = opt.DASH_RIGHT ? dashWidth : spacing; + let topBoxOffset = (opt.DASH_TOP ? dashHeight : spacing) + (opt.WS_TMB_TOP ? wsTmbHeight + spacing : 0); + const bottomBoxOffset = (opt.DASH_BOTTOM ? dashHeight : spacing) + (opt.WS_TMB_BOTTOM ? wsTmbHeight + spacing : 0); + const centeredBoxOffset = Math.max(leftBoxOffset, rightBoxOffset); + + // App grid needs to be calculated for the max wsTmbWidth in app grid, independently on the current wsTmb scale + const wsTmbHeightAppGrid = Math.round(height * opt.MAX_THUMBNAIL_SCALE_APPGRID); + const topBoxOffsetAppGrid = (opt.DASH_TOP ? dashHeight : spacing) + (opt.WS_TMB_TOP ? wsTmbHeightAppGrid + spacing : 0) + (opt.SHOW_SEARCH_ENTRY ? searchEntryHeight + spacing : 0); + const bottomBoxOffsetAppGrid = (opt.DASH_BOTTOM ? dashHeight : spacing) + (opt.WS_TMB_BOTTOM ? wsTmbHeightAppGrid + spacing : 0); + + // searchEntry + const searchEntryY = startY + topBoxOffset; + + const searchX = startX + + (opt.CENTER_SEARCH_VIEW || this._xAlignCenter + ? centeredBoxOffset + : leftBoxOffset); // xAlignCenter is set by wsBox + + const searchWidth = + width - (opt.CENTER_SEARCH_VIEW || this._xAlignCenter + ? 2 * centeredBoxOffset + : leftBoxOffset + rightBoxOffset); + + childBox.set_origin(searchX, searchEntryY); + childBox.set_size(searchWidth, searchEntryHeight); + + this._searchEntry.allocate(childBox); + + // searchResults + const searchY = startY + topBoxOffset + searchEntryHeight + spacing; + const searchHeight = height - topBoxOffset - bottomBoxOffset - searchEntryHeight - 2 * spacing; + + childBox.set_origin(searchX, searchY); + childBox.set_size(searchWidth, searchHeight); + this._searchController.allocate(childBox); + + // Add searchEntry height if needed + topBoxOffset += opt.SHOW_SEARCH_ENTRY ? searchEntryHeight + spacing : 0; + + // Workspace + let params = [ + box, + wsTmbWidth, + wsTmbHeight, + leftBoxOffset, + rightBoxOffset, + topBoxOffset, + bottomBoxOffset, + centeredBoxOffset, + ]; + + // Update cached boxes + for (const state of Object.values(ControlsState)) { + this._cachedWorkspaceBoxes.set( + state, this._computeWorkspacesBoxForState(state, ...params)); + } + + let workspacesBox; + 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); + workspacesBox = initialBox.interpolate(finalBox, transitionParams.progress); + } + + this._workspacesDisplay.allocate(workspacesBox); + + // appDisplay + params = [ + box, + leftBoxOffset === spacing ? 0 : leftBoxOffset, + rightBoxOffset === spacing ? 0 : rightBoxOffset, + topBoxOffsetAppGrid, + bottomBoxOffsetAppGrid, + ]; + let appDisplayBox; + if (!transitionParams.transitioning) { + appDisplayBox = + this._getAppDisplayBoxForState(transitionParams.currentState, ...params); + } else { + const initialBox = + this._getAppDisplayBoxForState(transitionParams.initialState, ...params); + const finalBox = + this._getAppDisplayBoxForState(transitionParams.finalState, ...params); + + appDisplayBox = initialBox.interpolate(finalBox, transitionParams.progress); + } + this._appDisplay.allocate(appDisplayBox); + + this._runPostAllocation(); + }, +}; + +// same copy of this function should be available in OverviewControls and WorkspacesView +function _getFitModeForState(state) { + switch (state) { + case ControlsState.HIDDEN: + case ControlsState.WINDOW_PICKER: + return FitMode.SINGLE; + case ControlsState.APP_GRID: + if (opt.WS_ANIMATION && opt.SHOW_WS_TMB) + return FitMode.ALL; + else + return FitMode.SINGLE; + default: + return FitMode.SINGLE; + } +} |