367 lines
15 KiB
JavaScript
367 lines
15 KiB
JavaScript
|
/*
|
||
|
This file is part of CoverflowAltTab.
|
||
|
|
||
|
CoverflowAltTab 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 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
CoverflowAltTab 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 CoverflowAltTab. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
/* CoverflowAltTab::CoverflowSwitcher:
|
||
|
*
|
||
|
* Extends CoverflowAltTab::Switcher, switching tabs using a cover flow.
|
||
|
*/
|
||
|
|
||
|
|
||
|
import Graphene from 'gi://Graphene';
|
||
|
|
||
|
import {Switcher} from './switcher.js';
|
||
|
const BaseSwitcher = Switcher;
|
||
|
import {Preview, Placement, Direction, findUpperLeftFromCenter} from './preview.js'
|
||
|
|
||
|
const SIDE_ANGLE = 90;
|
||
|
const BLEND_OUT_ANGLE = 30;
|
||
|
const ALPHA = 1;
|
||
|
|
||
|
function appendParams(base, extra) {
|
||
|
for (let key in extra) {
|
||
|
base[key] = extra[key];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export class CoverflowSwitcher extends BaseSwitcher {
|
||
|
constructor(...args) {
|
||
|
super(...args);
|
||
|
}
|
||
|
|
||
|
_createPreviews() {
|
||
|
// TODO: Shouldn't monitor be set once per coverflow state?
|
||
|
let monitor = this._updateActiveMonitor();
|
||
|
let currentWorkspace = this._manager.workspace_manager.get_active_workspace();
|
||
|
|
||
|
this._previewsCenterPosition = {
|
||
|
x: this.actor.width / 2,
|
||
|
y: this.actor.height / 2 + this._settings.offset
|
||
|
};
|
||
|
let ratio = this._settings.preview_to_monitor_ratio;
|
||
|
this._xOffsetLeft = this.actor.width * (0.5 * (1 - ratio) - 0.1 * ratio)
|
||
|
this._xOffsetRight = this.actor.width - this._xOffsetLeft;
|
||
|
|
||
|
for (let windowActor of global.get_window_actors()) {
|
||
|
let metaWin = windowActor.get_meta_window();
|
||
|
let compositor = metaWin.get_compositor_private();
|
||
|
if (compositor) {
|
||
|
let texture = compositor.get_texture();
|
||
|
let width, height;
|
||
|
if (texture.get_size) {
|
||
|
[width, height] = texture.get_size();
|
||
|
} else {
|
||
|
// TODO: Check this OK!
|
||
|
let preferred_size_ok;
|
||
|
[preferred_size_ok, width, height] = texture.get_preferred_size();
|
||
|
}
|
||
|
|
||
|
let scale = 1.0;
|
||
|
let previewScale = this._settings.preview_to_monitor_ratio;
|
||
|
let previewWidth = this.actor.width * previewScale;
|
||
|
let previewHeight = this.actor.height * previewScale;
|
||
|
if (width > previewWidth || height > previewHeight)
|
||
|
scale = Math.min(previewWidth / width, previewHeight / height);
|
||
|
|
||
|
let preview = new Preview(metaWin, this, {
|
||
|
name: metaWin.title,
|
||
|
opacity: ALPHA * (!metaWin.minimized && metaWin.get_workspace() == currentWorkspace || metaWin.is_on_all_workspaces()) ? 255 : 0,
|
||
|
source: texture.get_size ? texture : compositor,
|
||
|
reactive: true,
|
||
|
x: metaWin.minimized ? 0 :
|
||
|
compositor.x - monitor.x,
|
||
|
y: metaWin.minimized ? 0 :
|
||
|
compositor.y - monitor.y,
|
||
|
translation_x: 0,
|
||
|
width: width,
|
||
|
height: height,
|
||
|
scale_x: metaWin.minimized ? 0 : 1,
|
||
|
scale_y: metaWin.minimized ? 0 : 1,
|
||
|
scale_z: metaWin.minimized ? 0 : 1,
|
||
|
rotation_angle_y: 0,
|
||
|
});
|
||
|
preview.scale = scale;
|
||
|
preview.set_pivot_point_placement(Placement.CENTER);
|
||
|
preview.center_position = {
|
||
|
x: findUpperLeftFromCenter(width,
|
||
|
this._previewsCenterPosition.x),
|
||
|
y: findUpperLeftFromCenter(height,
|
||
|
this._previewsCenterPosition.y)
|
||
|
};
|
||
|
|
||
|
if (this._windows.includes(metaWin)) {
|
||
|
this._previews[this._windows.indexOf(metaWin)] = preview;
|
||
|
}
|
||
|
this._allPreviews.push(preview);
|
||
|
|
||
|
this.previewActor.add_child(preview);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_usingCarousel() {
|
||
|
return (this._parent === null && this._settings.switcher_looping_method == "Carousel");
|
||
|
}
|
||
|
|
||
|
_previewNext() {
|
||
|
if (this._currentIndex == this._windows.length - 1) {
|
||
|
this._setCurrentIndex(0);
|
||
|
if (this._usingCarousel()) {
|
||
|
this._updatePreviews(false)
|
||
|
} else {
|
||
|
this._flipStack(Direction.TO_LEFT);
|
||
|
}
|
||
|
} else {
|
||
|
this._setCurrentIndex(this._currentIndex + 1);
|
||
|
this._updatePreviews(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_previewPrevious() {
|
||
|
if (this._currentIndex == 0) {
|
||
|
this._setCurrentIndex(this._windows.length-1);
|
||
|
if (this._usingCarousel()) {
|
||
|
this._updatePreviews(false)
|
||
|
} else {
|
||
|
this._flipStack(Direction.TO_RIGHT);
|
||
|
}
|
||
|
} else {
|
||
|
this._setCurrentIndex(this._currentIndex - 1);
|
||
|
this._updatePreviews(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_flipStack(direction) {
|
||
|
//this._looping = true;
|
||
|
|
||
|
let xOffset, angle;
|
||
|
this._updateActiveMonitor();
|
||
|
if (direction === Direction.TO_LEFT) {
|
||
|
xOffset = -this._xOffsetLeft;
|
||
|
angle = BLEND_OUT_ANGLE;
|
||
|
} else {
|
||
|
xOffset = this._activeMonitor.width + this._xOffsetLeft;
|
||
|
angle = -BLEND_OUT_ANGLE;
|
||
|
}
|
||
|
|
||
|
let animation_time = this._settings.animation_time * 2/3;
|
||
|
|
||
|
for (let [i, preview] of this._previews.entries()) {
|
||
|
this._onFlipIn(preview, i, direction);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_onFlipIn(preview, index, direction) {
|
||
|
let xOffsetStart, xOffsetEnd, angleStart, angleEnd;
|
||
|
let zeroIndexPreview = null;
|
||
|
this._updateActiveMonitor();
|
||
|
|
||
|
if (direction === Direction.TO_LEFT) {
|
||
|
xOffsetStart = this.actor.width + this._xOffsetLeft;
|
||
|
xOffsetEnd = this._xOffsetRight;
|
||
|
angleStart = -BLEND_OUT_ANGLE;
|
||
|
angleEnd = -SIDE_ANGLE + this._getPerspectiveCorrectionAngle(1);
|
||
|
} else {
|
||
|
xOffsetStart = -this._xOffsetLeft;
|
||
|
xOffsetEnd = this._xOffsetLeft;
|
||
|
angleStart = BLEND_OUT_ANGLE;
|
||
|
angleEnd = SIDE_ANGLE + this._getPerspectiveCorrectionAngle(0);
|
||
|
}
|
||
|
|
||
|
//let animation_time = this._settings.animation_time * 2;
|
||
|
let animation_time = this._settings.animation_time * 2 * (direction === Direction.TO_RIGHT ? ((index + 1) / this._previews.length) : (1 - index / this._previews.length));
|
||
|
this._updatePreview(index, zeroIndexPreview, preview, index, false, animation_time);
|
||
|
let translation_x;
|
||
|
if (direction === Direction.TO_RIGHT) {
|
||
|
translation_x = xOffsetStart - (this._previewsCenterPosition.x
|
||
|
- preview.width / 2) + 50 * (index - this._currentIndex);
|
||
|
} else {
|
||
|
translation_x = xOffsetStart - (this._previewsCenterPosition.x
|
||
|
+ preview.width / 2) + 50 * (index - this._currentIndex);
|
||
|
}
|
||
|
let lastExtraParams = {
|
||
|
transition: 'userChoice',
|
||
|
onCompleteParams: [direction],
|
||
|
onComplete: this._onFlipComplete,
|
||
|
onCompleteScope: this
|
||
|
};
|
||
|
this._manager.platform.tween(preview, {
|
||
|
transition: 'easeInOutQuint',
|
||
|
opacity: ALPHA * 255,
|
||
|
time: animation_time,
|
||
|
});
|
||
|
this._raiseIcons();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
_onFlipComplete(direction) {
|
||
|
this._looping = false;
|
||
|
this._updatePreviews(false);
|
||
|
}
|
||
|
|
||
|
// TODO: Remove unused direction variable
|
||
|
_animatePreviewToMid(preview, animation_time, extraParams = []) {
|
||
|
let pivot_point = preview.get_pivot_point_placement(Placement.CENTER);
|
||
|
let tweenParams = {
|
||
|
x: findUpperLeftFromCenter(preview.width, this._previewsCenterPosition.x),
|
||
|
y: findUpperLeftFromCenter(preview.height, this._previewsCenterPosition.y),
|
||
|
scale_x: preview.scale,
|
||
|
scale_y: preview.scale,
|
||
|
scale_z: preview.scale,
|
||
|
pivot_point: pivot_point,
|
||
|
translation_x: 0,
|
||
|
rotation_angle_y: 0,
|
||
|
time: animation_time,
|
||
|
transition: 'userChoice',
|
||
|
};
|
||
|
appendParams(tweenParams, extraParams);
|
||
|
this._manager.platform.tween(preview, tweenParams);
|
||
|
}
|
||
|
|
||
|
_animatePreviewToSide(preview, index, xOffset, extraParams, toChangePivotPoint = true) {
|
||
|
let [x, y] = preview.get_pivot_point();
|
||
|
let pivot_point = new Graphene.Point({ x: x, y: y });
|
||
|
let half_length = Math.floor(this._previews.length / 2);
|
||
|
let pivot_index = (this._usingCarousel()) ?
|
||
|
half_length : this._currentIndex;
|
||
|
if (toChangePivotPoint) {
|
||
|
if (index < pivot_index) {
|
||
|
let progress = pivot_index - index < 1 ? pivot_index - index : 1;
|
||
|
pivot_point = new Graphene.Point({ x: 0.5 - 0.5 * progress, y: 0.5});
|
||
|
} else {
|
||
|
let progress = index - pivot_index < 1 ? index - pivot_index : 1;
|
||
|
pivot_point = new Graphene.Point({ x: 0.5 + 0.5 * progress, y: 0.5});
|
||
|
}
|
||
|
}
|
||
|
let scale = Math.pow(this._settings.preview_scaling_factor, Math.abs(index - pivot_index));
|
||
|
scale = scale * preview.scale;
|
||
|
let tweenParams = {
|
||
|
x: findUpperLeftFromCenter(preview.width, this._previewsCenterPosition.x),
|
||
|
y: findUpperLeftFromCenter(preview.height, this._previewsCenterPosition.y),
|
||
|
scale_x: scale,
|
||
|
scale_y: scale,
|
||
|
scale_z: scale,
|
||
|
pivot_point: pivot_point,
|
||
|
};
|
||
|
if (index < pivot_index) {
|
||
|
tweenParams.translation_x = xOffset - (this._previewsCenterPosition.x
|
||
|
- preview.width / 2) + 50 * (index - pivot_index);
|
||
|
} else {
|
||
|
tweenParams.translation_x = xOffset - (this._previewsCenterPosition.x
|
||
|
+ preview.width / 2) + 50 * (index - pivot_index);
|
||
|
}
|
||
|
appendParams(tweenParams, extraParams);
|
||
|
this._manager.platform.tween(preview, tweenParams);
|
||
|
}
|
||
|
|
||
|
_getPerspectiveCorrectionAngle(side) {
|
||
|
if (this._settings.perspective_correction_method != "Adjust Angles") return 0;
|
||
|
if (this.num_monitors == 1) {
|
||
|
return 0;
|
||
|
} else if (this.num_monitors == 2) {
|
||
|
if (this.monitor_number == this.monitors_ltr[0].index) {
|
||
|
if (side == 0) return 508/1000 * 90;
|
||
|
else return 508/1000 *90;
|
||
|
} else {
|
||
|
if (side == 0) return -508/1000 * 90;
|
||
|
else return -508/1000 * 90;
|
||
|
}
|
||
|
} else if (this.num_monitors == 3) {
|
||
|
if (this.monitor_number == this.monitors_ltr[0].index) {
|
||
|
if (side == 0) return (666)/1000 * 90;
|
||
|
else return 750/1000 * 90;
|
||
|
} else if (this.monitor_number == this.monitors_ltr[1].index) {
|
||
|
return 0;
|
||
|
} else {
|
||
|
if (side == 0) return (-750)/1000 * 90;
|
||
|
else return -666/1000 * 90;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_updatePreviews(reorder_only=false) {
|
||
|
if (this._previews == null) return;
|
||
|
let half_length = Math.floor(this._previews.length / 2);
|
||
|
let previews = [];
|
||
|
for (let [i, preview] of this._previews.entries()) {
|
||
|
let idx = (this._usingCarousel()) ?
|
||
|
(i - this._currentIndex + half_length + this._previews.length) % this._previews.length :
|
||
|
i;
|
||
|
previews.push([i, idx, preview]);
|
||
|
}
|
||
|
previews.sort((a, b) => a[1] - b[1]);
|
||
|
|
||
|
let zeroIndexPreview = null;
|
||
|
for (let item of previews) {
|
||
|
let preview = item[2];
|
||
|
let i = item[0];
|
||
|
let idx = item[1];
|
||
|
let animation_time = this._settings.animation_time * (this._settings.randomize_animation_times ? this._getRandomArbitrary(0.0001, 1) : 1);
|
||
|
zeroIndexPreview = this._updatePreview(idx, zeroIndexPreview, preview, i, reorder_only, animation_time);
|
||
|
this._manager.platform.tween(preview, {
|
||
|
opacity: ALPHA * 255,
|
||
|
time: this._settings.animation_time,
|
||
|
transition: 'easeInOutQuint',
|
||
|
onComplete: () => {
|
||
|
preview.set_reactive(true);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
if (zeroIndexPreview != null) zeroIndexPreview.make_bottom_layer(this.previewActor);
|
||
|
this._raiseIcons();
|
||
|
}
|
||
|
|
||
|
_updatePreview(idx, zeroIndexPreview, preview, i, reorder_only, animation_time) {
|
||
|
let half_length = Math.floor(this._previews.length / 2);
|
||
|
let pivot_index = (this._usingCarousel()) ?
|
||
|
half_length : this._currentIndex;
|
||
|
if (this._usingCarousel() && idx == 0) {
|
||
|
zeroIndexPreview = preview;
|
||
|
}
|
||
|
if (i == this._currentIndex) {
|
||
|
preview.make_top_layer(this.previewActor);
|
||
|
if (!reorder_only) {
|
||
|
this._animatePreviewToMid(preview, this._settings.animation_time);
|
||
|
}
|
||
|
} else if (idx < pivot_index) {
|
||
|
preview.make_top_layer(this.previewActor);
|
||
|
if (!reorder_only) {
|
||
|
let final_angle = SIDE_ANGLE + this._getPerspectiveCorrectionAngle(0);
|
||
|
let progress = pivot_index - idx < 1 ? pivot_index - idx : 1;
|
||
|
let center_offset = (this._xOffsetLeft + this._xOffsetRight) / 2;
|
||
|
this._animatePreviewToSide(preview, idx, center_offset - preview.width / 2 - progress * (center_offset - preview.width / 2 - this._xOffsetLeft), {
|
||
|
rotation_angle_y: progress * final_angle,
|
||
|
time: this.gestureInProgress ? 0 : animation_time,
|
||
|
transition: 'userChoice',
|
||
|
});
|
||
|
}
|
||
|
} else /* i > this._currentIndex */ {
|
||
|
preview.make_bottom_layer(this.previewActor);
|
||
|
if (!reorder_only) {
|
||
|
let final_angle = -SIDE_ANGLE + this._getPerspectiveCorrectionAngle(1);
|
||
|
let progress = idx - pivot_index < 1 ? idx - pivot_index : 1;
|
||
|
let center_offset = (this._xOffsetLeft + this._xOffsetRight) / 2;
|
||
|
this._animatePreviewToSide(preview, idx, center_offset + preview.width / 2 + progress * (this._xOffsetRight - center_offset - preview.width / 2), {
|
||
|
rotation_angle_y: progress * final_angle,
|
||
|
time: this.gestureInProgress ? 0 : animation_time,
|
||
|
transition: 'userChoice',
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
return zeroIndexPreview;
|
||
|
}
|
||
|
};
|