/* * This file is part of the Dash-To-Panel extension for Gnome 3 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import GObject from 'gi://GObject'; import Clutter from 'gi://Clutter'; import GLib from 'gi://GLib'; import Graphene from 'gi://Graphene'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import Meta from 'gi://Meta'; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; import St from 'gi://St'; import * as Taskbar from './taskbar.js'; import * as Utils from './utils.js'; import {SETTINGS, DESKTOPSETTINGS} from './extension.js'; import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js'; //timeout intervals const ENSURE_VISIBLE_MS = 200; //timeout names const T1 = 'openMenuTimeout'; const T2 = 'closeMenuTimeout'; const T3 = 'peekTimeout'; const T4 = 'ensureVisibleTimeout'; const MAX_TRANSLATION = 40; const HEADER_HEIGHT = 38; const MAX_CLOSE_BUTTON_SIZE = 30; const MIN_DIMENSION = 100; const FOCUSED_COLOR_OFFSET = 24; const HEADER_COLOR_OFFSET = -12; const FADE_SIZE = 36; const PEEK_INDEX_PROP = '_dtpPeekInitialIndex'; let headerHeight = 0; let alphaBg = 0; let isLeftButtons = false; let isTopHeader = true; let isManualStyling = false; let scaleFactor = 1; let animationTime = 0; let aspectRatio = {}; export const PreviewMenu = GObject.registerClass({ Signals: { 'open-state-changed': {} } }, class PreviewMenu extends St.Widget { _init(panel) { super._init({ layout_manager: new Clutter.BinLayout() }); let geom = panel.geom; this.panel = panel; this.currentAppIcon = null; this._focusedPreview = null; this._peekedWindow = null; this.allowCloseWindow = true; this.peekInitialWorkspaceIndex = -1; this.opened = false; this.isVertical = geom.position == St.Side.LEFT || geom.position == St.Side.RIGHT; this._translationProp = 'translation_' + (this.isVertical ? 'x' : 'y'); this._translationDirection = (geom.position == St.Side.TOP || geom.position == St.Side.LEFT ? -1 : 1); this._translationOffset = Math.min(panel.dtpSize, MAX_TRANSLATION) * this._translationDirection; this.menu = new St.Widget({ name: 'preview-menu', layout_manager: new Clutter.BinLayout(), reactive: true, track_hover: true, x_expand: true, y_expand: true, x_align: Clutter.ActorAlign[geom.position != St.Side.RIGHT ? 'START' : 'END'], y_align: Clutter.ActorAlign[geom.position != St.Side.BOTTOM ? 'START' : 'END'] }); this._box = new St.BoxLayout({ vertical: this.isVertical }); this._scrollView = new St.ScrollView({ name: 'dashtopanelPreviewScrollview', hscrollbar_policy: St.PolicyType.NEVER, vscrollbar_policy: St.PolicyType.NEVER, enable_mouse_scrolling: true, y_expand: !this.isVertical }); this._scrollView.add_actor(this._box); this.menu.add_child(this._scrollView); this.add_child(this.menu); } enable() { this._timeoutsHandler = new Utils.TimeoutsHandler(); this._signalsHandler = new Utils.GlobalSignalsHandler(); Main.layoutManager.addChrome(this, { affectsInputRegion: false }); Main.layoutManager.trackChrome(this.menu, { affectsInputRegion: true }); this._resetHiddenState(); this._refreshGlobals(); this._updateClip(); this.menu.set_position(1, 1); this._signalsHandler.add( [ this.menu, 'notify::hover', () => this._onHoverChanged() ], [ this._scrollView, 'scroll-event', this._onScrollEvent.bind(this) ], [ this.panel.panelBox, 'style-changed', () => this._updateClip() ], [ Utils.DisplayWrapper.getScreen(), 'in-fullscreen-changed', () => { if (global.display.focus_window && global.display.focus_window.is_fullscreen()) { this.close(true); } } ], [ SETTINGS, [ 'changed::panel-sizes', 'changed::window-preview-size', 'changed::window-preview-padding', 'changed::window-preview-show-title' ], () => { this._refreshGlobals(); this._updateClip(); } ] ); } disable() { this._timeoutsHandler.destroy(); this._signalsHandler.destroy(); this.close(true); Main.layoutManager.untrackChrome(this.menu); Main.layoutManager.removeChrome(this); } requestOpen(appIcon) { let timeout = SETTINGS.get_int('show-window-previews-timeout'); if (this.opened) { timeout = Math.min(100, timeout); } this._endOpenCloseTimeouts(); this._timeoutsHandler.add([T1, timeout, () => this.open(appIcon)]); } requestClose() { this._endOpenCloseTimeouts(); this._addCloseTimeout(); } open(appIcon, preventCloseWindow) { if (this.currentAppIcon != appIcon) { this.currentAppIcon = appIcon; this.allowCloseWindow = !preventCloseWindow; if (!this.opened) { this._refreshGlobals(); this.set_height(this.clipHeight); this.menu.show(); setStyle(this.menu, 'background: ' + Utils.getrgbaColor(this.panel.dynamicTransparency.backgroundColorRgb, alphaBg)); } this._mergeWindows(appIcon); this._updatePosition(); this._animateOpenOrClose(true); this._setReactive(true); this._setOpenedState(true); } } close(immediate) { this._endOpenCloseTimeouts(); this._removeFocus(); this._endPeek(); if (immediate) { Utils.stopAnimations(this.menu); this._resetHiddenState(); } else { this._animateOpenOrClose(false, () => this._resetHiddenState()); } this._setReactive(false); this.currentAppIcon = null; } update(appIcon, windows) { if (this.currentAppIcon == appIcon) { if (windows && !windows.length) { this.close(); } else { this._addAndRemoveWindows(windows); this._updatePosition(); } } } updatePosition() { this._updatePosition(); } focusNext() { let previews = this._box.get_children(); let currentIndex = this._focusedPreview ? previews.indexOf(this._focusedPreview) : -1; let nextIndex = currentIndex + 1; nextIndex = previews[nextIndex] ? nextIndex : 0; if (previews[nextIndex]) { this._removeFocus(); previews[nextIndex].setFocus(true); this._focusedPreview = previews[nextIndex]; } return nextIndex; } activateFocused() { if (this.opened && this._focusedPreview) { this._focusedPreview.activate(); } } requestPeek(window) { this._timeoutsHandler.remove(T3); if (SETTINGS.get_boolean('peek-mode')) { if (this.peekInitialWorkspaceIndex < 0) { this._timeoutsHandler.add([T3, SETTINGS.get_int('enter-peek-mode-timeout'), () => this._peek(window)]); } else { this._peek(window); } } } endPeekHere() { this._endPeek(true); } ensureVisible(preview) { let [ , upper, pageSize] = this._getScrollAdjustmentValues(); if (upper > pageSize) { this._timeoutsHandler.add([ T4, ENSURE_VISIBLE_MS, () => Utils.ensureActorVisibleInScrollView(this._scrollView, preview, MIN_DIMENSION, () => this._updateScrollFade()) ]); } } getCurrentAppIcon() { return this.currentAppIcon; } _setReactive(reactive) { this._box.get_children().forEach(c => c.reactive = reactive); this.menu.reactive = reactive; } _setOpenedState(opened) { this.opened = opened; this.emit('open-state-changed'); } _resetHiddenState() { this.menu.hide(); this.set_height(0); this._setOpenedState(false); this.menu.opacity = 0; this.menu[this._translationProp] = this._translationOffset; this._box.get_children().forEach(c => c.destroy()); } _removeFocus() { if (this._focusedPreview) { this._focusedPreview.setFocus(false); this._focusedPreview = null; } } _mergeWindows(appIcon, windows) { windows = windows || (appIcon.window ? [appIcon.window] : appIcon.getAppIconInterestingWindows()); windows.sort(Taskbar.sortWindowsCompareFunction); let currentPreviews = this._box.get_children(); let l = Math.max(windows.length, currentPreviews.length); for (let i = 0; i < l; ++i) { if (currentPreviews[i] && windows[i]) { currentPreviews[i].assignWindow(windows[i], this.opened); } else if (!currentPreviews[i]) { this._addNewPreview(windows[i]); } else if (!windows[i]) { currentPreviews[i][!this.opened ? 'destroy' : 'animateOut'](); } } } _addAndRemoveWindows(windows) { let currentPreviews = this._box.get_children(); windows.sort(Taskbar.sortWindowsCompareFunction); for (let i = 0, l = windows.length; i < l; ++i) { let currentIndex = Utils.findIndex(currentPreviews, c => c.window == windows[i]); if (currentIndex < 0) { this._addNewPreview(windows[i]); } else { currentPreviews[currentIndex].assignWindow(windows[i]); currentPreviews.splice(currentIndex, 1); if (this._peekedWindow && this._peekedWindow == windows[i]) { this.requestPeek(windows[i]); } } } currentPreviews.forEach(c => c.animateOut()); } _addNewPreview(window) { let preview = new Preview(this); this._box.add_child(preview); preview.adjustOnStage(); preview.assignWindow(window, this.opened); } _addCloseTimeout() { this._timeoutsHandler.add([T2, SETTINGS.get_int('leave-timeout'), () => this.close()]); } _onHoverChanged() { this._endOpenCloseTimeouts(); if (this.currentAppIcon && !this.menu.hover) { this._addCloseTimeout(); this._endPeek(); } } _onScrollEvent(actor, event) { if (!event.is_pointer_emulated()) { let vOrh = this.isVertical ? 'v' : 'h'; let adjustment = this._scrollView['get_' + vOrh + 'scroll_bar']().get_adjustment(); let increment = adjustment.step_increment; let delta = increment; switch (event.get_scroll_direction()) { case Clutter.ScrollDirection.UP: delta = -increment; break; case Clutter.ScrollDirection.SMOOTH: let [dx, dy] = event.get_scroll_delta(); delta = dy * increment; delta += dx * increment; break; } adjustment.set_value(adjustment.get_value() + delta); this._updateScrollFade(); } return Clutter.EVENT_STOP; } _endOpenCloseTimeouts() { this._timeoutsHandler.remove(T1); this._timeoutsHandler.remove(T2); this._timeoutsHandler.remove(T4); } _refreshGlobals() { isLeftButtons = Meta.prefs_get_button_layout().left_buttons.indexOf(Meta.ButtonFunction.CLOSE) >= 0; isTopHeader = SETTINGS.get_string('window-preview-title-position') == 'TOP'; isManualStyling = SETTINGS.get_boolean('window-preview-manual-styling'); scaleFactor = Utils.getScaleFactor(); headerHeight = SETTINGS.get_boolean('window-preview-show-title') ? HEADER_HEIGHT * scaleFactor : 0; animationTime = SETTINGS.get_int('window-preview-animation-time') * .001; aspectRatio.x = { size: SETTINGS.get_int('window-preview-aspect-ratio-x'), fixed: SETTINGS.get_boolean('window-preview-fixed-x') }; aspectRatio.y = { size: SETTINGS.get_int('window-preview-aspect-ratio-y'), fixed: SETTINGS.get_boolean('window-preview-fixed-y') }; alphaBg = SETTINGS.get_boolean('preview-use-custom-opacity') ? SETTINGS.get_int('preview-custom-opacity') * .01 : this.panel.dynamicTransparency.alpha; } _updateClip() { let x, y, w; let geom = this.panel.getGeometry(); let panelBoxTheme = this.panel.panelBox.get_theme_node(); let previewSize = (SETTINGS.get_int('window-preview-size') + SETTINGS.get_int('window-preview-padding') * 2) * scaleFactor; if (this.isVertical) { w = previewSize; this.clipHeight = this.panel.monitor.height; y = this.panel.monitor.y; } else { w = this.panel.monitor.width; this.clipHeight = (previewSize + headerHeight); x = this.panel.monitor.x; } if (geom.position == St.Side.LEFT) { x = this.panel.monitor.x + this.panel.dtpSize + panelBoxTheme.get_padding(St.Side.LEFT); } else if (geom.position == St.Side.RIGHT) { x = this.panel.monitor.x + this.panel.monitor.width - (this.panel.dtpSize + previewSize) - panelBoxTheme.get_padding(St.Side.RIGHT); } else if (geom.position == St.Side.TOP) { y = this.panel.monitor.y + this.panel.dtpSize + panelBoxTheme.get_padding(St.Side.TOP); } else { //St.Side.BOTTOM y = this.panel.monitor.y + this.panel.monitor.height - (this.panel.dtpSize + panelBoxTheme.get_padding(St.Side.BOTTOM) + previewSize + headerHeight); } Utils.setClip(this, x, y, w, this.clipHeight); } _updatePosition() { let sourceNode = this.currentAppIcon.get_theme_node(); let sourceContentBox = sourceNode.get_content_box(this.currentAppIcon.get_allocation_box()); let sourceAllocation = Utils.getTransformedAllocation(this.currentAppIcon); let [previewsWidth, previewsHeight] = this._getPreviewsSize(); let appIconMargin = SETTINGS.get_int('appicon-margin') / scaleFactor; let x = 0, y = 0; previewsWidth = Math.min(previewsWidth, this.panel.monitor.width); previewsHeight = Math.min(previewsHeight, this.panel.monitor.height); this._updateScrollFade(previewsWidth < this.panel.monitor.width && previewsHeight < this.panel.monitor.height); if (this.isVertical) { y = sourceAllocation.y1 + appIconMargin - this.panel.monitor.y + (sourceContentBox.y2 - sourceContentBox.y1 - previewsHeight) * .5; y = Math.max(y, 0); y = Math.min(y, this.panel.monitor.height - previewsHeight); } else { x = sourceAllocation.x1 + appIconMargin - this.panel.monitor.x + (sourceContentBox.x2 - sourceContentBox.x1 - previewsWidth) * .5; x = Math.max(x, 0); x = Math.min(x, this.panel.monitor.width - previewsWidth); } if (!this.opened) { this.menu.set_position(x, y); this.menu.set_size(previewsWidth, previewsHeight); } else { Utils.animate(this.menu, getTweenOpts({ x: x, y: y, width: previewsWidth, height: previewsHeight })); } } _updateScrollFade(remove) { let [value, upper, pageSize] = this._getScrollAdjustmentValues(); let needsFade = Math.round(upper) > Math.round(pageSize); let fadeWidgets = this.menu.get_children().filter(c => c != this._scrollView); if (!remove && needsFade) { if (!fadeWidgets.length) { fadeWidgets.push(this._getFadeWidget()); fadeWidgets.push(this._getFadeWidget(true)); this.menu.add_child(fadeWidgets[0]); this.menu.add_child(fadeWidgets[1]); } fadeWidgets[0].visible = value > 0; fadeWidgets[1].visible = value + pageSize < upper; } else if (remove || (!needsFade && fadeWidgets.length)) { fadeWidgets.forEach(fw => fw.destroy()); } } _getScrollAdjustmentValues() { let [value , , upper, , , pageSize] = this._scrollView[(this.isVertical ? 'v' : 'h') + 'scroll'].adjustment.get_values(); return [value, upper, pageSize]; } _getFadeWidget(end) { let x = 0, y = 0; let startBg = Utils.getrgbaColor(this.panel.dynamicTransparency.backgroundColorRgb, Math.min(alphaBg + .1, 1)); let endBg = Utils.getrgbaColor(this.panel.dynamicTransparency.backgroundColorRgb, 0) let fadeStyle = 'background-gradient-start:' + startBg + 'background-gradient-end:' + endBg + 'background-gradient-direction:' + this.panel.getOrientation(); if (this.isVertical) { y = end ? this.panel.monitor.height - FADE_SIZE : 0; } else { x = end ? this.panel.monitor.width - FADE_SIZE : 0; } let fadeWidget = new St.Widget({ reactive: false, pivot_point: new Graphene.Point({ x: .5, y: .5 }), rotation_angle_z: end ? 180 : 0, style: fadeStyle, x: x, y: y, width: this.isVertical ? this.width : FADE_SIZE, height: this.isVertical ? FADE_SIZE : this.height }); return fadeWidget; } _getPreviewsSize() { let previewsWidth = 0; let previewsHeight = 0; this._box.get_children().forEach(c => { if (!c.animatingOut) { let [width, height] = c.getSize(); if (this.isVertical) { previewsWidth = Math.max(width, previewsWidth); previewsHeight += height; } else { previewsWidth += width; previewsHeight = Math.max(height, previewsHeight); } } }); return [previewsWidth, previewsHeight]; } _animateOpenOrClose(show, onComplete) { let isTranslationAnimation = this.menu[this._translationProp] != 0; let tweenOpts = { opacity: show ? 255 : 0, transition: show ? 'easeInOutQuad' : 'easeInCubic', onComplete: () => { if (isTranslationAnimation) { Main.layoutManager._queueUpdateRegions(); } (onComplete || (() => {}))(); } }; tweenOpts[this._translationProp] = show ? this._translationDirection : this._translationOffset; Utils.animate(this.menu, getTweenOpts(tweenOpts)); } _peek(window) { let currentWorkspace = Utils.getCurrentWorkspace(); let windowWorkspace = window.get_workspace(); let focusWindow = () => this._focusMetaWindow(SETTINGS.get_int('peek-mode-opacity'), window); this._restorePeekedWindowStack(); if (this._peekedWindow && windowWorkspace != currentWorkspace) { currentWorkspace.list_windows().forEach(mw => this.animateWindowOpacity(mw, null, 255)) } this._peekedWindow = window; if (currentWorkspace != windowWorkspace) { this._switchToWorkspaceImmediate(windowWorkspace.index()); this._timeoutsHandler.add([T3, 100, focusWindow]); } else { focusWindow(); } if (this.peekInitialWorkspaceIndex < 0) { this.peekInitialWorkspaceIndex = currentWorkspace.index(); } } _endPeek(stayHere) { this._timeoutsHandler.remove(T3); if (this._peekedWindow) { let immediate = !stayHere && this.peekInitialWorkspaceIndex != Utils.getCurrentWorkspace().index(); this._restorePeekedWindowStack(); this._focusMetaWindow(255, this._peekedWindow, immediate, true); this._peekedWindow = null; if (!stayHere) { this._switchToWorkspaceImmediate(this.peekInitialWorkspaceIndex); } this.peekInitialWorkspaceIndex = -1; } } _switchToWorkspaceImmediate(workspaceIndex) { let workspace = Utils.getWorkspaceByIndex(workspaceIndex); let shouldAnimate = Main.wm._shouldAnimate; if (!workspace || (!workspace.list_windows().length && workspaceIndex < Utils.getWorkspaceCount() - 1)) { workspace = Utils.getCurrentWorkspace(); } Main.wm._shouldAnimate = () => false; workspace.activate(global.display.get_current_time_roundtrip()); Main.wm._shouldAnimate = shouldAnimate; } _focusMetaWindow(dimOpacity, window, immediate, ignoreFocus) { window.get_workspace().list_windows().forEach(mw => { let wa = mw.get_compositor_private(); let isFocused = !ignoreFocus && mw == window; if (wa) { if (isFocused) { mw[PEEK_INDEX_PROP] = wa.get_parent().get_children().indexOf(wa); wa.get_parent().set_child_above_sibling(wa, null); } if (isFocused && mw.minimized) { wa.show(); } this.animateWindowOpacity(mw, wa, isFocused ? 255 : dimOpacity, immediate) } }); } animateWindowOpacity(metaWindow, windowActor, opacity, immediate) { windowActor = windowActor || metaWindow.get_compositor_private(); if (windowActor && !metaWindow.minimized) { let tweenOpts = getTweenOpts({ opacity }); if (immediate && !metaWindow.is_on_all_workspaces()) { tweenOpts.time = 0; } Utils.animateWindowOpacity(windowActor, tweenOpts); } } _restorePeekedWindowStack() { let windowActor = this._peekedWindow ? this._peekedWindow.get_compositor_private() : null; if (windowActor) { if (this._peekedWindow.hasOwnProperty(PEEK_INDEX_PROP)) { windowActor.get_parent().set_child_at_index(windowActor, this._peekedWindow[PEEK_INDEX_PROP]); delete this._peekedWindow[PEEK_INDEX_PROP]; } if (this._peekedWindow.minimized) { windowActor.hide(); } } } }); export const Preview = GObject.registerClass({ }, class Preview extends St.Widget { _init(previewMenu) { super._init({ style_class: 'preview-container', reactive: true, track_hover: true, layout_manager: new Clutter.BinLayout() }); this.window = null; this._waitWindowId = 0; this._needsCloseButton = true; this.cloneWidth = this.cloneHeight = 0; this._previewMenu = previewMenu; this._padding = SETTINGS.get_int('window-preview-padding') * scaleFactor; this._previewDimensions = this._getPreviewDimensions(); this.animatingOut = false; let box = new St.Widget({ layout_manager: new Clutter.BoxLayout({ orientation: Clutter.Orientation.VERTICAL }), y_expand: true }); let [previewBinWidth, previewBinHeight] = this._getBinSize(); let closeButton = new St.Button({ style_class: 'window-close', accessible_name: 'Close window' }); closeButton.add_actor(new St.Icon({ icon_name: 'window-close-symbolic' })); this._closeButtonBin = new St.Widget({ style_class: 'preview-close-btn-container', layout_manager: new Clutter.BinLayout(), opacity: 0, x_expand: true, y_expand: true, x_align: Clutter.ActorAlign[isLeftButtons ? 'START' : 'END'], y_align: Clutter.ActorAlign[isTopHeader ? 'START' : 'END'] }); this._closeButtonBin.add_child(closeButton); this._previewBin = new St.Widget({ layout_manager: new Clutter.BinLayout(), x_expand: true, y_expand: true, style: 'padding: ' + this._padding / scaleFactor + 'px;' }); this._previewBin.set_size(previewBinWidth, previewBinHeight); box.add_child(this._previewBin); if (headerHeight) { let headerBox = new St.Widget({ style_class: 'preview-header-box', layout_manager: new Clutter.BoxLayout(), x_expand: true, y_align: Clutter.ActorAlign[isTopHeader ? 'START' : 'END'] }); setStyle(headerBox, this._getBackgroundColor(HEADER_COLOR_OFFSET, 1)); this._workspaceIndicator = new St.Label({ y_align: Clutter.ActorAlign.CENTER }); this._windowTitle = new St.Label({ y_align: Clutter.ActorAlign.CENTER, x_expand: true }); this._iconBin = new St.Widget({ layout_manager: new Clutter.BinLayout() }); this._iconBin.set_size(headerHeight, headerHeight); headerBox.add_child(this._iconBin); headerBox.insert_child_at_index(this._workspaceIndicator, isLeftButtons ? 0 : 1); headerBox.insert_child_at_index(this._windowTitle, isLeftButtons ? 1 : 2); box.insert_child_at_index(headerBox, isTopHeader ? 0 : 1); } this.add_child(box); this.add_child(this._closeButtonBin); closeButton.connect('clicked', () => this._onCloseBtnClick()); this.connect('notify::hover', () => this._onHoverChanged()); this.connect('button-release-event', (actor, e) => this._onButtonReleaseEvent(e)); this.connect('destroy', () => this._onDestroy()); } adjustOnStage() { let closeButton = this._closeButtonBin.get_first_child(); let closeButtonHeight = closeButton.height; let maxCloseButtonSize = MAX_CLOSE_BUTTON_SIZE * scaleFactor; let closeButtonBorderRadius = ''; if (closeButtonHeight > maxCloseButtonSize) { closeButtonHeight = maxCloseButtonSize; closeButton.set_size(closeButtonHeight, closeButtonHeight); } if (!headerHeight) { closeButtonBorderRadius = 'border-radius: '; if (isTopHeader) { closeButtonBorderRadius += (isLeftButtons ? '0 0 4px 0;' : '0 0 0 4px;'); } else { closeButtonBorderRadius += (isLeftButtons ? '0 4px 0 0;' : '4px 0 0 0;'); } } setStyle( this._closeButtonBin, 'padding: ' + (headerHeight ? Math.round((headerHeight - closeButtonHeight) * .5 / scaleFactor) : 4) + 'px;' + this._getBackgroundColor(HEADER_COLOR_OFFSET, headerHeight ? 1 : .6) + closeButtonBorderRadius ); } assignWindow(window, animateSize) { if (this.window != window) { let _assignWindowClone = () => { if (window.get_compositor_private()) { let cloneBin = this._getWindowCloneBin(window); this._resizeClone(cloneBin, window); this._addClone(cloneBin, animateSize); this._previewMenu.updatePosition(); } else if (!this._waitWindowId) { this._waitWindowId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { this._waitWindowId = 0; if (this._previewMenu.opened) { _assignWindowClone(); } return GLib.SOURCE_REMOVE; }); } }; _assignWindowClone(); } this._cancelAnimateOut(); this._removeWindowSignals(); this.window = window; this._needsCloseButton = this._previewMenu.allowCloseWindow && window.can_close() && !Utils.checkIfWindowHasTransient(window); this._updateHeader(); } animateOut() { if (!this.animatingOut) { let tweenOpts = getTweenOpts({ opacity: 0, width: 0, height: 0, onComplete: () => this.destroy() }); this.animatingOut = true; Utils.stopAnimations(this); Utils.animate(this, tweenOpts); } } getSize() { let [binWidth, binHeight] = this._getBinSize(); binWidth = Math.max(binWidth, this.cloneWidth + this._padding * 2); binHeight = Math.max(binHeight, this.cloneHeight + this._padding * 2) + headerHeight; return [binWidth, binHeight]; } setFocus(focused) { this._hideOrShowCloseButton(!focused); setStyle(this, this._getBackgroundColor(FOCUSED_COLOR_OFFSET, focused ? '-' : 0)); if (focused) { this._previewMenu.ensureVisible(this); this._previewMenu.requestPeek(this.window); } } activate() { this._previewMenu.endPeekHere(); this._previewMenu.close(); Main.activateWindow(this.window); } _onDestroy() { if (this._waitWindowId) { GLib.source_remove(this._waitWindowId); this._waitWindowId = 0; } this._removeWindowSignals(); } _onHoverChanged() { this.setFocus(this.hover); } _onCloseBtnClick() { this._hideOrShowCloseButton(true); this.reactive = false; if (!SETTINGS.get_boolean('group-apps')) { this._previewMenu.close(); } else { this._previewMenu.endPeekHere(); } this.window.delete(global.get_current_time()); } _onButtonReleaseEvent(e) { switch (e.get_button()) { case 1: // Left click this.activate(); break; case 2: // Middle click if (SETTINGS.get_boolean('preview-middle-click-close')) { this._onCloseBtnClick(); } break; case 3: // Right click this._showContextMenu(e); break; } return Clutter.EVENT_STOP; } _cancelAnimateOut() { if (this.animatingOut) { this.animatingOut = false; Utils.stopAnimations(this); Utils.animate(this, getTweenOpts({ opacity: 255, width: this.cloneWidth, height: this.cloneHeight })); } } _showContextMenu(e) { let coords = e.get_coords(); let currentWorkspace = this._previewMenu.peekInitialWorkspaceIndex < 0 ? Utils.getCurrentWorkspace() : Utils.getWorkspaceByIndex(this._previewMenu.peekInitialWorkspaceIndex); Main.wm._showWindowMenu(null, this.window, Meta.WindowMenuType.WM, { x: coords[0], y: coords[1], width: 0, height: 0 }); let menu = Main.wm._windowMenuManager._manager._menus[0]; menu.connect('open-state-changed', () => this._previewMenu.menu.sync_hover()); this._previewMenu.menu.sync_hover(); if (this.window.get_workspace() != currentWorkspace) { let menuItem = new PopupMenu.PopupMenuItem(_('Move to current Workspace') + ' [' + (currentWorkspace.index() + 1) + ']'); let menuItems = menu.box.get_children(); let insertIndex = Utils.findIndex(menuItems, c => c._delegate instanceof PopupMenu.PopupSeparatorMenuItem); insertIndex = insertIndex >= 0 ? insertIndex : menuItems.length - 1; menu.addMenuItem(menuItem, insertIndex); menuItem.connect('activate', () => this.window.change_workspace(currentWorkspace)); } } _removeWindowSignals() { if (this._titleWindowChangeId) { this.window.disconnect(this._titleWindowChangeId); this._titleWindowChangeId = 0; } } _updateHeader() { if (headerHeight) { let iconTextureSize = SETTINGS.get_boolean('window-preview-use-custom-icon-size') ? SETTINGS.get_int('window-preview-custom-icon-size') : headerHeight / scaleFactor * .6; let icon = this._previewMenu.getCurrentAppIcon().app.create_icon_texture(iconTextureSize); let workspaceIndex = ''; let workspaceStyle = null; let fontScale = DESKTOPSETTINGS.get_double('text-scaling-factor'); let commonTitleStyles = 'color: ' + SETTINGS.get_string('window-preview-title-font-color') + ';' + 'font-size: ' + SETTINGS.get_int('window-preview-title-font-size') * fontScale + 'px;' + 'font-weight: ' + SETTINGS.get_string('window-preview-title-font-weight') + ';'; this._iconBin.destroy_all_children(); this._iconBin.add_child(icon); if (!SETTINGS.get_boolean('isolate-workspaces')) { workspaceIndex = (this.window.get_workspace().index() + 1).toString(); workspaceStyle = 'margin: 0 4px 0 ' + (isLeftButtons ? Math.round((headerHeight - icon.width) * .5) + 'px' : '0') + '; padding: 0 4px;' + 'border: 2px solid ' + this._getRgbaColor(FOCUSED_COLOR_OFFSET, .8) + 'border-radius: 2px;' + commonTitleStyles; } this._workspaceIndicator.text = workspaceIndex; setStyle(this._workspaceIndicator, workspaceStyle); this._titleWindowChangeId = this.window.connect('notify::title', () => this._updateWindowTitle()); setStyle(this._windowTitle, 'max-width: 0px; padding-right: 4px;' + commonTitleStyles); this._updateWindowTitle(); } } _updateWindowTitle() { this._windowTitle.text = this.window.title; } _hideOrShowCloseButton(hide) { if (this._needsCloseButton) { Utils.animate(this._closeButtonBin, getTweenOpts({ opacity: hide ? 0 : 255 })); } } _getBackgroundColor(offset, alpha) { return 'background-color: ' + this._getRgbaColor(offset, alpha) + 'transition-duration:' + this._previewMenu.panel.dynamicTransparency.animationDuration; } _getRgbaColor(offset, alpha) { alpha = Math.abs(alpha); if (isNaN(alpha)) { alpha = alphaBg; } return Utils.getrgbaColor(this._previewMenu.panel.dynamicTransparency.backgroundColorRgb, alpha, offset); } _addClone(newCloneBin, animateSize) { let currentClones = this._previewBin.get_children(); let newCloneOpts = getTweenOpts({ opacity: 255 }); this._previewBin.add_child(newCloneBin); if (currentClones.length) { let currentCloneBin = currentClones.pop(); let currentCloneOpts = getTweenOpts({ opacity: 0, onComplete: () => currentCloneBin.destroy() }); if (newCloneBin.width > currentCloneBin.width) { newCloneOpts.width = newCloneBin.width; newCloneBin.width = currentCloneBin.width; } else { currentCloneOpts.width = newCloneBin.width; } if (newCloneBin.height > currentCloneBin.height) { newCloneOpts.height = newCloneBin.height; newCloneBin.height = currentCloneBin.height; } else { currentCloneOpts.height = newCloneBin.height; } currentClones.forEach(c => c.destroy()); Utils.animate(currentCloneBin, currentCloneOpts); } else if (animateSize) { newCloneBin.width = 0; newCloneBin.height = 0; newCloneOpts.width = this.cloneWidth; newCloneOpts.height = this.cloneHeight; } Utils.animate(newCloneBin, newCloneOpts); } _getWindowCloneBin(window) { let frameRect = window.get_frame_rect(); let bufferRect = window.get_buffer_rect(); let clone = new Clutter.Clone({ source: window.get_compositor_private() }); let cloneBin = new St.Widget({ opacity: 0, layout_manager: frameRect.width != bufferRect.width || frameRect.height != bufferRect.height ? new WindowCloneLayout(frameRect, bufferRect) : new Clutter.BinLayout() }); cloneBin.add_child(clone); return cloneBin; } _getBinSize() { let [fixedWidth, fixedHeight] = this._previewDimensions; return [ aspectRatio.x.fixed ? fixedWidth + this._padding * 2 : -1, aspectRatio.y.fixed ? fixedHeight + this._padding * 2 : -1 ]; } _resizeClone(cloneBin, window) { let frameRect = cloneBin.layout_manager.frameRect || window.get_frame_rect(); let [fixedWidth, fixedHeight] = this._previewDimensions; let ratio = Math.min(fixedWidth / frameRect.width, fixedHeight / frameRect.height, 1); let cloneWidth = frameRect.width * ratio; let cloneHeight = frameRect.height * ratio; let clonePaddingTB = cloneHeight < MIN_DIMENSION ? MIN_DIMENSION - cloneHeight : 0; let clonePaddingLR = cloneWidth < MIN_DIMENSION ? MIN_DIMENSION - cloneWidth : 0; let clonePaddingTop = clonePaddingTB * .5; let clonePaddingLeft = clonePaddingLR * .5; this.cloneWidth = cloneWidth + clonePaddingLR * scaleFactor; this.cloneHeight = cloneHeight + clonePaddingTB * scaleFactor; cloneBin.set_style('padding: ' + clonePaddingTop + 'px ' + clonePaddingLeft + 'px;'); cloneBin.layout_manager.ratio = ratio; cloneBin.layout_manager.padding = [clonePaddingLeft * scaleFactor, clonePaddingTop * scaleFactor]; cloneBin.get_first_child().set_size(cloneWidth, cloneHeight); } _getPreviewDimensions() { let size = SETTINGS.get_int('window-preview-size') * scaleFactor; let w, h; if (this._previewMenu.isVertical) { w = size; h = w * aspectRatio.y.size / aspectRatio.x.size; } else { h = size; w = h * aspectRatio.x.size / aspectRatio.y.size; } return [w, h]; } }); export const WindowCloneLayout = GObject.registerClass({ }, class WindowCloneLayout extends Clutter.BinLayout { _init(frameRect, bufferRect) { super._init(); //the buffer_rect contains the transparent padding that must be removed this.frameRect = frameRect; this.bufferRect = bufferRect; } vfunc_allocate(actor, box) { let [width, height] = box.get_size(); box.set_origin( (this.bufferRect.x - this.frameRect.x) * this.ratio + this.padding[0], (this.bufferRect.y - this.frameRect.y) * this.ratio + this.padding[1] ); box.set_size( width + (this.bufferRect.width - this.frameRect.width) * this.ratio, height + (this.bufferRect.height - this.frameRect.height) * this.ratio ); actor.get_first_child().allocate(box); } }); export function setStyle(actor, style) { if (!isManualStyling) { actor.set_style(style); } } export function getTweenOpts(opts) { let defaults = { time: animationTime, transition: 'easeInOutQuad' }; return Utils.mergeObjects(opts || {}, defaults); }