mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-11 16:57:43 +00:00
155 lines
5.3 KiB
JavaScript
155 lines
5.3 KiB
JavaScript
|
|
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
||
|
|
// SPDX-License-Identifier: EUPL-1.2
|
||
|
|
/*global effect, effects, animate, animationTime, cancel, Effect, QEasingCurve */
|
||
|
|
|
||
|
|
"use strict";
|
||
|
|
|
||
|
|
class ShiftTileAnimationsEffect {
|
||
|
|
constructor() {
|
||
|
|
effect.configChanged.connect(this.loadConfig.bind(this));
|
||
|
|
effect.animationEnded.connect(this.cleanupWindow.bind(this));
|
||
|
|
effects.windowAdded.connect(this.manage.bind(this));
|
||
|
|
effects.windowClosed.connect(this.cleanupWindow.bind(this));
|
||
|
|
|
||
|
|
this.loadConfig();
|
||
|
|
|
||
|
|
for (const window of effects.stackingOrder) {
|
||
|
|
this.manage(window);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
loadConfig() {
|
||
|
|
this.baseDuration = effect.readConfig("Duration", 185) || 185;
|
||
|
|
this.minimumDelta = effect.readConfig("MinimumDelta", 3) || 3;
|
||
|
|
}
|
||
|
|
|
||
|
|
manage(window) {
|
||
|
|
if (!window || window.shiftTileAnimationsManaged) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
window.shiftTileAnimationsManaged = true;
|
||
|
|
window.shiftTileUserMoveResize = false;
|
||
|
|
window.windowFrameGeometryChanged.connect(this.onWindowFrameGeometryChanged.bind(this));
|
||
|
|
window.windowStartUserMovedResized.connect(this.onWindowStartUserMovedResized.bind(this));
|
||
|
|
window.windowFinishUserMovedResized.connect(this.onWindowFinishUserMovedResized.bind(this));
|
||
|
|
}
|
||
|
|
|
||
|
|
eligibleWindow(window) {
|
||
|
|
return window
|
||
|
|
&& !effects.hasActiveFullScreenEffect
|
||
|
|
&& window.visible
|
||
|
|
&& !window.deleted
|
||
|
|
&& window.managed
|
||
|
|
&& window.normalWindow
|
||
|
|
&& !window.fullScreen
|
||
|
|
&& !window.desktopWindow
|
||
|
|
&& !window.dock
|
||
|
|
&& !window.popup
|
||
|
|
&& !window.popupWindow
|
||
|
|
&& !window.outline;
|
||
|
|
}
|
||
|
|
|
||
|
|
validGeometry(geometry) {
|
||
|
|
return geometry && geometry.width > 0 && geometry.height > 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
movedEnough(oldGeometry, newGeometry) {
|
||
|
|
return Math.abs(oldGeometry.x - newGeometry.x) >= this.minimumDelta
|
||
|
|
|| Math.abs(oldGeometry.y - newGeometry.y) >= this.minimumDelta
|
||
|
|
|| Math.abs(oldGeometry.width - newGeometry.width) >= this.minimumDelta
|
||
|
|
|| Math.abs(oldGeometry.height - newGeometry.height) >= this.minimumDelta;
|
||
|
|
}
|
||
|
|
|
||
|
|
durationFor(oldGeometry, newGeometry) {
|
||
|
|
const oldCenterX = oldGeometry.x + oldGeometry.width / 2;
|
||
|
|
const oldCenterY = oldGeometry.y + oldGeometry.height / 2;
|
||
|
|
const newCenterX = newGeometry.x + newGeometry.width / 2;
|
||
|
|
const newCenterY = newGeometry.y + newGeometry.height / 2;
|
||
|
|
const distanceX = newCenterX - oldCenterX;
|
||
|
|
const distanceY = newCenterY - oldCenterY;
|
||
|
|
const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
|
||
|
|
const resize = Math.abs(newGeometry.width - oldGeometry.width) + Math.abs(newGeometry.height - oldGeometry.height);
|
||
|
|
const travelAllowance = Math.min(80, Math.round(Math.max(distance / 12, resize / 18)));
|
||
|
|
|
||
|
|
return animationTime(Math.max(135, Math.min(260, this.baseDuration + travelAllowance)));
|
||
|
|
}
|
||
|
|
|
||
|
|
cancelWindowAnimation(window) {
|
||
|
|
if (!window || window.shiftTileMoveAnimation === undefined) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
cancel(window.shiftTileMoveAnimation);
|
||
|
|
window.shiftTileMoveAnimation = undefined;
|
||
|
|
window.setData(Effect.WindowForceBlurRole, null);
|
||
|
|
}
|
||
|
|
|
||
|
|
cleanupWindow(window) {
|
||
|
|
if (!window) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
window.shiftTileMoveAnimation = undefined;
|
||
|
|
window.setData(Effect.WindowForceBlurRole, null);
|
||
|
|
}
|
||
|
|
|
||
|
|
onWindowStartUserMovedResized(window) {
|
||
|
|
if (!window) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
window.shiftTileUserMoveResize = true;
|
||
|
|
this.cancelWindowAnimation(window);
|
||
|
|
}
|
||
|
|
|
||
|
|
onWindowFinishUserMovedResized(window) {
|
||
|
|
if (window) {
|
||
|
|
window.shiftTileUserMoveResize = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
onWindowFrameGeometryChanged(window, oldGeometry) {
|
||
|
|
if (!this.eligibleWindow(window) || window.shiftTileUserMoveResize) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const newGeometry = window.geometry;
|
||
|
|
if (!this.validGeometry(oldGeometry) || !this.validGeometry(newGeometry) || !this.movedEnough(oldGeometry, newGeometry)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.cancelWindowAnimation(window);
|
||
|
|
window.setData(Effect.WindowForceBlurRole, true);
|
||
|
|
window.shiftTileMoveAnimation = animate({
|
||
|
|
window: window,
|
||
|
|
duration: this.durationFor(oldGeometry, newGeometry),
|
||
|
|
keepAlive: false,
|
||
|
|
animations: [{
|
||
|
|
type: Effect.Size,
|
||
|
|
from: {
|
||
|
|
value1: oldGeometry.width,
|
||
|
|
value2: oldGeometry.height
|
||
|
|
},
|
||
|
|
to: {
|
||
|
|
value1: newGeometry.width,
|
||
|
|
value2: newGeometry.height
|
||
|
|
},
|
||
|
|
curve: QEasingCurve.OutCubic
|
||
|
|
}, {
|
||
|
|
type: Effect.Translation,
|
||
|
|
from: {
|
||
|
|
value1: oldGeometry.x - newGeometry.x - (newGeometry.width / 2 - oldGeometry.width / 2),
|
||
|
|
value2: oldGeometry.y - newGeometry.y - (newGeometry.height / 2 - oldGeometry.height / 2)
|
||
|
|
},
|
||
|
|
to: {
|
||
|
|
value1: 0,
|
||
|
|
value2: 0
|
||
|
|
},
|
||
|
|
curve: QEasingCurve.OutCubic
|
||
|
|
}]
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
new ShiftTileAnimationsEffect();
|