summaryrefslogtreecommitdiffstats
path: root/extensions/vertical-workspaces/windowPreview.js
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/vertical-workspaces/windowPreview.js')
-rw-r--r--extensions/vertical-workspaces/windowPreview.js335
1 files changed, 335 insertions, 0 deletions
diff --git a/extensions/vertical-workspaces/windowPreview.js b/extensions/vertical-workspaces/windowPreview.js
new file mode 100644
index 0000000..013f48c
--- /dev/null
+++ b/extensions/vertical-workspaces/windowPreview.js
@@ -0,0 +1,335 @@
+/**
+ * Vertical Workspaces
+ * windowPreview.js
+ *
+ * @author GdH <G-dH@github.com>
+ * @copyright 2022 - 2023
+ * @license GPL-3.0
+ *
+ */
+
+'use strict';
+
+const { Clutter, GLib, Graphene, Meta, Shell, St } = imports.gi;
+
+const Main = imports.ui.main;
+const WindowPreview = imports.ui.windowPreview;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const Me = ExtensionUtils.getCurrentExtension();
+
+const _Util = Me.imports.util;
+let _overrides;
+
+const WINDOW_SCALE_TIME = imports.ui.windowPreview.WINDOW_SCALE_TIME;
+const WINDOW_ACTIVE_SIZE_INC = imports.ui.windowPreview.WINDOW_ACTIVE_SIZE_INC;
+const WINDOW_OVERLAY_FADE_TIME = imports.ui.windowPreview.WINDOW_OVERLAY_FADE_TIME;
+const SEARCH_WINDOWS_PREFIX = Me.imports.windowSearchProvider.prefix;
+
+const ControlsState = imports.ui.overviewControls.ControlsState;
+
+var opt = null;
+
+function update(reset = false) {
+ if (_overrides) {
+ _overrides.removeAll();
+ }
+
+ if (reset) {
+ _overrides = null;
+ opt = null;
+ return;
+ }
+
+ opt = Me.imports.settings.opt;
+ _overrides = new _Util.Overrides();
+
+ _overrides.addOverride('WindowPreview', WindowPreview.WindowPreview.prototype, WindowPreviewCommon);
+ // move titles into window previews
+ _overrides.addInjection('WindowPreview', WindowPreview.WindowPreview.prototype, WindowPreviewInjections);
+}
+
+//----- WindowPreview ------------------------------------------------------------------
+var WindowPreviewInjections = {
+ _init: function() {
+ const ICON_OVERLAP = 0.7;
+
+ if (opt.WIN_PREVIEW_ICON_SIZE < 64) {
+ this.remove_child(this._icon);
+ this._icon.destroy();
+ const tracker = Shell.WindowTracker.get_default();
+ const app = tracker.get_window_app(this.metaWindow);
+ this._icon = app.create_icon_texture(opt.WIN_PREVIEW_ICON_SIZE);
+ this._icon.add_style_class_name('icon-dropshadow');
+ this._icon.set({
+ reactive: true,
+ pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
+ });
+ this._icon.add_constraint(new Clutter.BindConstraint({
+ source: this.windowContainer,
+ coordinate: Clutter.BindCoordinate.POSITION,
+ }));
+ this._icon.add_constraint(new Clutter.AlignConstraint({
+ source: this.windowContainer,
+ align_axis: Clutter.AlignAxis.X_AXIS,
+ factor: 0.5,
+ }));
+ this._icon.add_constraint(new Clutter.AlignConstraint({
+ source: this.windowContainer,
+ align_axis: Clutter.AlignAxis.Y_AXIS,
+ pivot_point: new Graphene.Point({ x: -1, y: ICON_OVERLAP }),
+ factor: 1,
+ }));
+ this.add_child(this._icon);
+ if (opt.WIN_PREVIEW_ICON_SIZE < 22) {
+ // disable app icon
+ this._icon.hide();
+ }
+ this._iconSize = opt.WIN_PREVIEW_ICON_SIZE;
+ }
+
+ const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
+ const iconOverlap = opt.WIN_PREVIEW_ICON_SIZE * ICON_OVERLAP;
+ // we cannot get proper title height before it gets to the stage, so 35 is estimated height + spacing
+ this._title.get_constraints()[1].offset = scaleFactor * (- iconOverlap - 35);
+ this.set_child_above_sibling(this._title, null);
+ // if window is created while the overview is shown, icon and title should be visible immediately
+ if (Main.overview._overview._controls._stateAdjustment.value < 1) {
+ this._icon.scale_x = 0;
+ this._icon.scale_y = 0;
+ this._title.opacity = 0;
+ }
+
+ if (opt.ALWAYS_SHOW_WIN_TITLES) {
+ this._title.show();
+ this._stateConId = this._workspace._background._stateAdjustment.connect('notify::value', (adj) => {
+ this._title.opacity = Math.floor(adj.value) * 255;
+ });
+
+ }
+
+ if (opt.OVERVIEW_MODE === 1) {
+ // spread windows on hover
+ this._wsStateConId = this.connect('enter-event', () => {
+ // don't spread windows if user don't use pointer device at this moment
+ if (global.get_pointer()[0] === opt.showingPointerX)
+ return;
+
+ const adjustment = this._workspace._background._stateAdjustment;
+ if (!adjustment.value && !Main.overview._animationInProgress) {
+ opt.WORKSPACE_MODE = 1;
+ if (adjustment.value === 0) {
+ adjustment.value = 0;
+ adjustment.ease(1, {
+ duration: 200,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD
+ });
+ }
+ }
+ });
+ }
+ if (opt.OVERVIEW_MODE) {
+ // show window icon and title on ws windows spread
+ this._stateAdjustmentSigId = this._workspace.stateAdjustment.connect('notify::value', this._updateIconScale.bind(this));
+ }
+
+ // replace click action with custom one
+ const action = this.get_actions()[0];
+ this.remove_action(action);
+
+ const clickAction = new Clutter.ClickAction();
+ clickAction.connect('clicked', (action) => {
+ const button = action.get_button();
+ if (button === Clutter.BUTTON_PRIMARY) {
+ this._activate();
+ } else if (button === Clutter.BUTTON_SECONDARY) {
+ // this action cancels long-press event and the 'long-press-cancel' event is used by the Shell to actually initiate DnD
+ // so the dnd initiation needs to be removed
+ if (this._longPressLater) {
+ Meta.later_remove(this._longPressLater);
+ delete this._longPressLater;
+ }
+ const tracker = Shell.WindowTracker.get_default();
+ const appName = tracker.get_window_app(this.metaWindow).get_name();
+ _Util.activateSearchProvider(`${SEARCH_WINDOWS_PREFIX} ${appName}`);
+ return Clutter.EVENT_STOP;
+ }
+ });
+
+ clickAction.connect('long-press', this._onLongPress.bind(this));
+ this.add_action(clickAction);
+
+ this.connect('destroy', () => this._workspace.stateAdjustment.disconnect(this._stateConId));
+ }
+}
+
+// WindowPreview
+var WindowPreviewCommon = {
+ _updateIconScale: function() {
+ let { currentState, initialState, finalState } =
+ this._overviewAdjustment.getStateTransitionParams();
+
+ // Current state - 0 - HIDDEN, 1 - WINDOW_PICKER, 2 - APP_GRID
+ const primaryMonitor = this.metaWindow.get_monitor() === global.display.get_primary_monitor();
+
+ const visible =
+ (initialState > ControlsState.HIDDEN || finalState > ControlsState.HIDDEN)
+ && !(finalState === ControlsState.APP_GRID && primaryMonitor);
+
+ let scale = visible
+ ? (currentState >= 1 ? 1 : currentState % 1) : 0;
+ if (!primaryMonitor &&
+ ((initialState === ControlsState.WINDOW_PICKER && finalState === ControlsState.APP_GRID) ||
+ (initialState === ControlsState.APP_GRID && finalState === ControlsState.WINDOW_PICKER))
+ ) {
+ scale = 1;
+ /*} else if (primaryMonitor && ((initialState === ControlsState.WINDOW_PICKER && finalState === ControlsState.APP_GRID) ||
+ initialState === ControlsState.APP_GRID && finalState === ControlsState.HIDDEN)) {*/
+ } else if (primaryMonitor && currentState > ControlsState.WINDOW_PICKER) {
+ scale = 0;
+ }
+
+ // in static workspace mode show icon and title on ws windows spread
+ if (opt.OVERVIEW_MODE) {
+ const windowsSpread = this._workspace.stateAdjustment.value;
+ if (currentState === 1) {
+ scale = windowsSpread;
+ } else if (finalState === 1 || (finalState === 0 && !windowsSpread)) {
+ return;
+ }
+ }
+
+ this._icon.set({
+ scale_x: scale,
+ scale_y: scale,
+ });
+
+ // if titles are in 'always show' mode, we need to add transition between visible/invisible state
+ // but the transition is quite expensive,
+ // showing the titles at the end of the transition is good enough and workspace preview transition is much smoother
+ this._title.set({
+ opacity: Math.floor(scale) * 255,
+ });
+ },
+
+ showOverlay: function(animate) {
+ if (!this._overlayEnabled)
+ return;
+
+ if (this._overlayShown)
+ return;
+
+ this._overlayShown = true;
+ //this._restack();
+
+ // If we're supposed to animate and an animation in our direction
+ // is already happening, let that one continue
+ const ongoingTransition = this._title.get_transition('opacity');
+ if (animate &&
+ ongoingTransition &&
+ ongoingTransition.get_interval().peek_final_value() === 255)
+ return;
+
+ const toShow = this._windowCanClose()
+ ? [this._closeButton]
+ : [];
+
+ if (!opt.ALWAYS_SHOW_WIN_TITLES) {
+ toShow.push(this._title);
+ }
+
+ toShow.forEach(a => {
+ a.opacity = 0;
+ a.show();
+ a.ease({
+ opacity: 255,
+ duration: animate ? WINDOW_OVERLAY_FADE_TIME : 0,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ });
+ });
+
+ const [width, height] = this.window_container.get_size();
+ const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
+ const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * 2 * scaleFactor;
+ const origSize = Math.max(width, height);
+ const scale = (origSize + activeExtraSize) / origSize;
+
+ this.window_container.ease({
+ scale_x: scale,
+ scale_y: scale,
+ duration: animate ? WINDOW_SCALE_TIME : 0,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ });
+
+ this.emit('show-chrome');
+ },
+
+ hideOverlay: function(animate) {
+ if (!this._overlayShown)
+ return;
+ this._overlayShown = false;
+ //this._restack();
+
+ // If we're supposed to animate and an animation in our direction
+ // is already happening, let that one continue
+ const ongoingTransition = this._title.get_transition('opacity');
+ if (animate &&
+ ongoingTransition &&
+ ongoingTransition.get_interval().peek_final_value() === 0)
+ return;
+
+ const toHide = [this._closeButton];
+
+ if (!opt.ALWAYS_SHOW_WIN_TITLES) {
+ toHide.push(this._title);
+ }
+ toHide.forEach(a => {
+ a.opacity = 255;
+ a.ease({
+ opacity: 0,
+ duration: animate ? WINDOW_OVERLAY_FADE_TIME : 0,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ onComplete: () => a.hide(),
+ });
+ });
+
+ if (this.window_container) {
+ this.window_container.ease({
+ scale_x: 1,
+ scale_y: 1,
+ duration: animate ? WINDOW_SCALE_TIME : 0,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ });
+ }
+ },
+
+ _onDestroy() {
+ // fix for upstream bug - hideOverlay is called after windowPreview is destroyed, from the leave event callback
+ // but it still throws:
+ // clutter_actor_get_preferred_width: assertion 'CLUTTER_IS_ACTOR (self)' failed
+ // clutter_actor_get_preferred_height: assertion 'CLUTTER_IS_ACTOR (self)' failed
+ this.hideOverlay(false);
+
+ this.metaWindow._delegate = null;
+ this._delegate = null;
+
+ if (this._longPressLater) {
+ Meta.later_remove(this._longPressLater);
+ delete this._longPressLater;
+ }
+
+ if (this._idleHideOverlayId > 0) {
+ GLib.source_remove(this._idleHideOverlayId);
+ this._idleHideOverlayId = 0;
+ }
+
+ if (this.inDrag) {
+ this.emit('drag-end');
+ this.inDrag = false;
+ }
+
+ if (this._stateAdjustmentSigId) {
+ this._workspace.stateAdjustment.disconnect(this._stateAdjustmentSigId);
+ }
+ }
+}