193 lines
6.2 KiB
JavaScript
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;
|