glass/src/window/smoothMovementManager.js
2025-07-15 20:10:46 +09:00

193 lines
6.2 KiB
JavaScript

const { screen } = require('electron');
class SmoothMovementManager {
constructor(windowPool) {
this.windowPool = windowPool;
this.stepSize = 80;
this.animationDuration = 300;
this.headerPosition = { x: 0, y: 0 };
this.isAnimating = false;
this.hiddenPosition = null;
this.lastVisiblePosition = null;
this.currentDisplayId = null;
this.animationFrameId = null;
this.animationTimers = new Map();
}
/**
* @param {BrowserWindow} win
* @returns {boolean}
*/
_isWindowValid(win) {
if (!win || win.isDestroyed()) {
// 해당 창의 타이머가 있으면 정리
if (this.animationTimers.has(win)) {
clearTimeout(this.animationTimers.get(win));
this.animationTimers.delete(win);
}
return false;
}
return true;
}
/**
*
* @param {BrowserWindow} win
* @param {number} targetX
* @param {number} targetY
* @param {object} [options]
* @param {object} [options.sizeOverride]
* @param {function} [options.onComplete]
* @param {number} [options.duration]
*/
animateWindow(win, targetX, targetY, options = {}) {
if (!this._isWindowValid(win)) {
if (options.onComplete) options.onComplete();
return;
}
const { sizeOverride, onComplete, duration: animDuration } = options;
const start = win.getBounds();
const startTime = Date.now();
const duration = animDuration || this.animationDuration;
const { width, height } = sizeOverride || start;
const step = () => {
if (!this._isWindowValid(win)) {
if (onComplete) onComplete();
return;
}
const p = Math.min((Date.now() - startTime) / duration, 1);
const eased = 1 - Math.pow(1 - p, 3); // ease-out-cubic
const x = start.x + (targetX - start.x) * eased;
const y = start.y + (targetY - start.y) * eased;
win.setBounds({ x: Math.round(x), y: Math.round(y), width, height });
if (p < 1) {
setTimeout(step, 8);
} else {
this.layoutManager.updateLayout();
if (onComplete) {
onComplete();
}
}
};
step();
}
fade(win, { from, to, duration = 250, onComplete }) {
if (!this._isWindowValid(win)) {
if (onComplete) onComplete();
return;
}
const startOpacity = from ?? win.getOpacity();
const startTime = Date.now();
const step = () => {
if (!this._isWindowValid(win)) {
if (onComplete) onComplete(); return;
}
const progress = Math.min(1, (Date.now() - startTime) / duration);
const eased = 1 - Math.pow(1 - progress, 3);
win.setOpacity(startOpacity + (to - startOpacity) * eased);
if (progress < 1) {
setTimeout(step, 8);
} else {
win.setOpacity(to);
if (onComplete) onComplete();
}
};
step();
}
animateWindowBounds(win, targetBounds, options = {}) {
if (this.animationTimers.has(win)) {
clearTimeout(this.animationTimers.get(win));
}
if (!this._isWindowValid(win)) {
if (options.onComplete) options.onComplete();
return;
}
this.isAnimating = true;
const startBounds = win.getBounds();
const startTime = Date.now();
const duration = options.duration || this.animationDuration;
const step = () => {
if (!this._isWindowValid(win)) {
if (options.onComplete) options.onComplete();
return;
}
const progress = Math.min(1, (Date.now() - startTime) / duration);
const eased = 1 - Math.pow(1 - progress, 3);
const newBounds = {
x: Math.round(startBounds.x + (targetBounds.x - startBounds.x) * eased),
y: Math.round(startBounds.y + (targetBounds.y - startBounds.y) * eased),
width: Math.round(startBounds.width + ((targetBounds.width ?? startBounds.width) - startBounds.width) * eased),
height: Math.round(startBounds.height + ((targetBounds.height ?? startBounds.height) - startBounds.height) * eased),
};
win.setBounds(newBounds);
if (progress < 1) {
const timerId = setTimeout(step, 8);
this.animationTimers.set(win, timerId);
} else {
win.setBounds(targetBounds);
this.animationTimers.delete(win);
if (this.animationTimers.size === 0) {
this.isAnimating = false;
}
if (options.onComplete) options.onComplete();
}
};
step();
}
animateWindowPosition(win, targetPosition, options = {}) {
if (!this._isWindowValid(win)) {
if (options.onComplete) options.onComplete();
return;
}
const currentBounds = win.getBounds();
const targetBounds = { ...currentBounds, ...targetPosition };
this.animateWindowBounds(win, targetBounds, options);
}
animateLayout(layout, animated = true) {
if (!layout) return;
for (const winName in layout) {
const win = this.windowPool.get(winName);
const targetBounds = layout[winName];
if (win && !win.isDestroyed() && targetBounds) {
if (animated) {
this.animateWindowBounds(win, targetBounds);
} else {
win.setBounds(targetBounds);
}
}
}
}
destroy() {
if (this.animationFrameId) {
clearTimeout(this.animationFrameId);
this.animationFrameId = null;
}
this.isAnimating = false;
console.log('[Movement] Manager destroyed');
}
}
module.exports = SmoothMovementManager;