refactor windowmanager finished

This commit is contained in:
sanio 2025-07-15 20:10:46 +09:00
parent ecae4050bb
commit bba38ac56f
3 changed files with 94 additions and 761 deletions

View File

@ -1,28 +1,8 @@
const { screen } = require('electron'); const { screen } = require('electron');
function getCurrentDisplay(window) {
if (!window || window.isDestroyed()) return screen.getPrimaryDisplay();
const windowBounds = window.getBounds();
const windowCenter = {
x: windowBounds.x + windowBounds.width / 2,
y: windowBounds.y + windowBounds.height / 2,
};
return screen.getDisplayNearestPoint(windowCenter);
}
function getDisplayById(displayId) {
const displays = screen.getAllDisplays();
return displays.find(d => d.id === displayId) || screen.getPrimaryDisplay();
}
class SmoothMovementManager { class SmoothMovementManager {
// constructor(windowPool, layoutManager) {
constructor(windowPool) { constructor(windowPool) {
this.windowPool = windowPool; this.windowPool = windowPool;
// this.layoutManager = layoutManager;
this.stepSize = 80; this.stepSize = 80;
this.animationDuration = 300; this.animationDuration = 300;
this.headerPosition = { x: 0, y: 0 }; this.headerPosition = { x: 0, y: 0 };
@ -31,6 +11,8 @@ class SmoothMovementManager {
this.lastVisiblePosition = null; this.lastVisiblePosition = null;
this.currentDisplayId = null; this.currentDisplayId = null;
this.animationFrameId = null; this.animationFrameId = null;
this.animationTimers = new Map();
} }
/** /**
@ -39,114 +21,25 @@ class SmoothMovementManager {
*/ */
_isWindowValid(win) { _isWindowValid(win) {
if (!win || win.isDestroyed()) { if (!win || win.isDestroyed()) {
if (this.isAnimating) { // 해당 창의 타이머가 있으면 정리
console.warn('[MovementManager] Window destroyed mid-animation. Halting.'); if (this.animationTimers.has(win)) {
this.isAnimating = false; clearTimeout(this.animationTimers.get(win));
if (this.animationFrameId) { this.animationTimers.delete(win);
clearTimeout(this.animationFrameId);
this.animationFrameId = null;
}
} }
return false; return false;
} }
return true; return true;
} }
// moveToDisplay(displayId) {
// const header = this.windowPool.get('header');
// if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return;
// const targetDisplay = getDisplayById(displayId);
// if (!targetDisplay) return;
// const currentBounds = header.getBounds();
// const currentDisplay = getCurrentDisplay(header);
// if (currentDisplay.id === targetDisplay.id) return;
// const relativeX = (currentBounds.x - currentDisplay.workArea.x) / currentDisplay.workAreaSize.width;
// const relativeY = (currentBounds.y - currentDisplay.workArea.y) / currentDisplay.workAreaSize.height;
// const targetX = targetDisplay.workArea.x + targetDisplay.workAreaSize.width * relativeX;
// const targetY = targetDisplay.workArea.y + targetDisplay.workAreaSize.height * relativeY;
// const finalX = Math.max(targetDisplay.workArea.x, Math.min(targetDisplay.workArea.x + targetDisplay.workAreaSize.width - currentBounds.width, targetX));
// const finalY = Math.max(targetDisplay.workArea.y, Math.min(targetDisplay.workArea.y + targetDisplay.workAreaSize.height - currentBounds.height, targetY));
// this.headerPosition = { x: currentBounds.x, y: currentBounds.y };
// this.animateToPosition(header, finalX, finalY);
// this.currentDisplayId = targetDisplay.id;
// }
// moveStep(direction) {
// const header = this.windowPool.get('header');
// if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return;
// const currentBounds = header.getBounds();
// this.headerPosition = { x: currentBounds.x, y: currentBounds.y };
// let targetX = this.headerPosition.x;
// let targetY = this.headerPosition.y;
// console.log(`[MovementManager] Moving ${direction} from (${targetX}, ${targetY})`);
// const windowSize = {
// width: currentBounds.width,
// height: currentBounds.height
// };
// switch (direction) {
// case 'left': targetX -= this.stepSize; break;
// case 'right': targetX += this.stepSize; break;
// case 'up': targetY -= this.stepSize; break;
// case 'down': targetY += this.stepSize; break;
// default: return;
// }
// // Find the display that contains or is nearest to the target position
// const nearestDisplay = screen.getDisplayNearestPoint({ x: targetX, y: targetY });
// const { x: workAreaX, y: workAreaY, width: workAreaWidth, height: workAreaHeight } = nearestDisplay.workArea;
// // Only clamp if the target position would actually go out of bounds
// let clampedX = targetX;
// let clampedY = targetY;
// // Check horizontal bounds
// if (targetX < workAreaX) {
// clampedX = workAreaX;
// } else if (targetX + currentBounds.width > workAreaX + workAreaWidth) {
// clampedX = workAreaX + workAreaWidth - currentBounds.width;
// }
// // Check vertical bounds
// if (targetY < workAreaY) {
// clampedY = workAreaY;
// console.log(`[MovementManager] Clamped Y to top edge: ${clampedY}`);
// } else if (targetY + currentBounds.height > workAreaY + workAreaHeight) {
// clampedY = workAreaY + workAreaHeight - currentBounds.height;
// console.log(`[MovementManager] Clamped Y to bottom edge: ${clampedY}`);
// }
// console.log(`[MovementManager] Final position: (${clampedX}, ${clampedY}), Work area: ${workAreaX},${workAreaY} ${workAreaWidth}x${workAreaHeight}`);
// // Only move if there's an actual change in position
// if (clampedX === this.headerPosition.x && clampedY === this.headerPosition.y) {
// console.log(`[MovementManager] No position change, skipping animation`);
// return;
// }
// this.animateToPosition(header, clampedX, clampedY, windowSize);
// }
/** /**
* [수정됨] 창을 목표 지점으로 부드럽게 애니메이션합니다. *
* 완료 콜백 기타 옵션을 지원합니다. * @param {BrowserWindow} win
* @param {BrowserWindow} win - 애니메이션할 * @param {number} targetX
* @param {number} targetX - 목표 X 좌표 * @param {number} targetY
* @param {number} targetY - 목표 Y 좌표 * @param {object} [options]
* @param {object} [options] - 추가 옵션 * @param {object} [options.sizeOverride]
* @param {object} [options.sizeOverride] - 애니메이션 사용할 크기 * @param {function} [options.onComplete]
* @param {function} [options.onComplete] - 애니메이션 완료 실행할 콜백 * @param {number} [options.duration]
* @param {number} [options.duration] - 애니메이션 지속 시간 (ms)
*/ */
animateWindow(win, targetX, targetY, options = {}) { animateWindow(win, targetX, targetY, options = {}) {
if (!this._isWindowValid(win)) { if (!this._isWindowValid(win)) {
@ -161,7 +54,6 @@ class SmoothMovementManager {
const { width, height } = sizeOverride || start; const { width, height } = sizeOverride || start;
const step = () => { const step = () => {
// 애니메이션 중간에 창이 파괴될 경우 콜백을 실행하고 중단
if (!this._isWindowValid(win)) { if (!this._isWindowValid(win)) {
if (onComplete) onComplete(); if (onComplete) onComplete();
return; return;
@ -175,114 +67,17 @@ class SmoothMovementManager {
win.setBounds({ x: Math.round(x), y: Math.round(y), width, height }); win.setBounds({ x: Math.round(x), y: Math.round(y), width, height });
if (p < 1) { if (p < 1) {
setTimeout(step, 8); // requestAnimationFrame 대신 setTimeout으로 간결하게 처리 setTimeout(step, 8);
} else { } else {
// 애니메이션 종료 this.layoutManager.updateLayout();
this.layoutManager.updateLayout(); // 레이아웃 재정렬
if (onComplete) { if (onComplete) {
onComplete(); // 완료 콜백 실행 onComplete();
} }
} }
}; };
step(); step();
} }
// animateToPosition(header, targetX, targetY, windowSize) {
// if (!this._isWindowValid(header)) return;
// this.isAnimating = true;
// const startX = this.headerPosition.x;
// const startY = this.headerPosition.y;
// const startTime = Date.now();
// if (!Number.isFinite(targetX) || !Number.isFinite(targetY) || !Number.isFinite(startX) || !Number.isFinite(startY)) {
// this.isAnimating = false;
// return;
// }
// const animate = () => {
// if (!this._isWindowValid(header)) return;
// const elapsed = Date.now() - startTime;
// const progress = Math.min(elapsed / this.animationDuration, 1);
// const eased = 1 - Math.pow(1 - progress, 3);
// const currentX = startX + (targetX - startX) * eased;
// const currentY = startY + (targetY - startY) * eased;
// if (!Number.isFinite(currentX) || !Number.isFinite(currentY)) {
// this.isAnimating = false;
// return;
// }
// if (!this._isWindowValid(header)) return;
// const { width, height } = windowSize || header.getBounds();
// header.setBounds({
// x: Math.round(currentX),
// y: Math.round(currentY),
// width,
// height
// });
// if (progress < 1) {
// this.animationFrameId = setTimeout(animate, 8);
// } else {
// this.animationFrameId = null;
// this.isAnimating = false;
// if (Number.isFinite(targetX) && Number.isFinite(targetY)) {
// if (!this._isWindowValid(header)) return;
// header.setPosition(Math.round(targetX), Math.round(targetY));
// // Update header position to the actual final position
// this.headerPosition = { x: Math.round(targetX), y: Math.round(targetY) };
// }
// this.layoutManager.updateLayout();
// }
// };
// animate();
// }
// moveToEdge(direction) {
// const header = this.windowPool.get('header');
// if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return;
// const display = getCurrentDisplay(header);
// const { width, height } = display.workAreaSize;
// const { x: workAreaX, y: workAreaY } = display.workArea;
// const currentBounds = header.getBounds();
// const windowSize = {
// width: currentBounds.width,
// height: currentBounds.height
// };
// let targetX = currentBounds.x;
// let targetY = currentBounds.y;
// switch (direction) {
// case 'left':
// targetX = workAreaX;
// break;
// case 'right':
// targetX = workAreaX + width - windowSize.width;
// break;
// case 'up':
// targetY = workAreaY;
// break;
// case 'down':
// targetY = workAreaY + height - windowSize.height;
// break;
// }
// header.setBounds({
// x: Math.round(targetX),
// y: Math.round(targetY),
// width: windowSize.width,
// height: windowSize.height
// });
// this.headerPosition = { x: targetX, y: targetY };
// this.layoutManager.updateLayout();
// }
fade(win, { from, to, duration = 250, onComplete }) { fade(win, { from, to, duration = 250, onComplete }) {
if (!this._isWindowValid(win)) { if (!this._isWindowValid(win)) {
if (onComplete) onComplete(); if (onComplete) onComplete();
@ -310,18 +105,23 @@ class SmoothMovementManager {
} }
animateWindowBounds(win, targetBounds, options = {}) { animateWindowBounds(win, targetBounds, options = {}) {
if (this.animationTimers.has(win)) {
clearTimeout(this.animationTimers.get(win));
}
if (!this._isWindowValid(win)) { if (!this._isWindowValid(win)) {
if (options.onComplete) options.onComplete(); if (options.onComplete) options.onComplete();
return; return;
} }
this.isAnimating = true; this.isAnimating = true;
const startBounds = win.getBounds(); const startBounds = win.getBounds();
const startTime = Date.now(); const startTime = Date.now();
const duration = options.duration || this.animationDuration; const duration = options.duration || this.animationDuration;
const step = () => { const step = () => {
if (!this._isWindowValid(win)) { if (!this._isWindowValid(win)) {
this.isAnimating = false;
if (options.onComplete) options.onComplete(); if (options.onComplete) options.onComplete();
return; return;
} }
@ -338,10 +138,16 @@ class SmoothMovementManager {
win.setBounds(newBounds); win.setBounds(newBounds);
if (progress < 1) { if (progress < 1) {
setTimeout(step, 8); const timerId = setTimeout(step, 8);
this.animationTimers.set(win, timerId);
} else { } else {
win.setBounds(targetBounds); win.setBounds(targetBounds);
this.isAnimating = false; this.animationTimers.delete(win);
if (this.animationTimers.size === 0) {
this.isAnimating = false;
}
if (options.onComplete) options.onComplete(); if (options.onComplete) options.onComplete();
} }
}; };

View File

@ -27,16 +27,6 @@ class WindowLayoutManager {
this.PADDING = 80; this.PADDING = 80;
} }
// updateLayout() {
// if (this.isUpdating) return;
// this.isUpdating = true;
// setImmediate(() => {
// this.positionWindows();
// this.isUpdating = false;
// });
// }
getHeaderPosition = () => { getHeaderPosition = () => {
const header = this.windowPool.get('header'); const header = this.windowPool.get('header');
if (header) { if (header) {
@ -46,215 +36,6 @@ class WindowLayoutManager {
return { x: 0, y: 0 }; return { x: 0, y: 0 };
}; };
// resizeHeaderWindow = ({ width, height }) => {
// const header = this.windowPool.get('header');
// if (header) {
// console.log(`[WindowManager] Resize request: ${width}x${height}`);
// const currentBounds = header.getBounds();
// console.log(`[WindowManager] Current bounds: ${currentBounds.width}x${currentBounds.height} at (${currentBounds.x}, ${currentBounds.y})`);
// if (currentBounds.width === width && currentBounds.height === height) {
// console.log('[WindowManager] Already at target size, skipping resize');
// return { success: true };
// }
// const wasResizable = header.isResizable();
// if (!wasResizable) {
// header.setResizable(true);
// }
// const centerX = currentBounds.x + currentBounds.width / 2;
// const newX = Math.round(centerX - width / 2);
// const display = getCurrentDisplay(header);
// const { x: workAreaX, width: workAreaWidth } = display.workArea;
// const clampedX = Math.max(workAreaX, Math.min(workAreaX + workAreaWidth - width, newX));
// header.setBounds({ x: clampedX, y: currentBounds.y, width, height });
// if (!wasResizable) {
// header.setResizable(false);
// }
// this.updateLayout();
// return { success: true };
// }
// return { success: false, error: 'Header window not found' };
// };
// moveHeaderTo = (newX, newY) => {
// const header = this.windowPool.get('header');
// if (header) {
// const targetDisplay = screen.getDisplayNearestPoint({ x: newX, y: newY });
// const { x: workAreaX, y: workAreaY, width, height } = targetDisplay.workArea;
// const headerBounds = header.getBounds();
// let clampedX = newX;
// let clampedY = newY;
// if (newX < workAreaX) {
// clampedX = workAreaX;
// } else if (newX + headerBounds.width > workAreaX + width) {
// clampedX = workAreaX + width - headerBounds.width;
// }
// if (newY < workAreaY) {
// clampedY = workAreaY;
// } else if (newY + headerBounds.height > workAreaY + height) {
// clampedY = workAreaY + height - headerBounds.height;
// }
// header.setPosition(clampedX, clampedY, false);
// this.updateLayout();
// }
// };
// adjustWindowHeight = (sender, targetHeight) => {
// const senderWindow = this.windowPool.get(sender);
// if (senderWindow) {
// const wasResizable = senderWindow.isResizable();
// if (!wasResizable) {
// senderWindow.setResizable(true);
// }
// const currentBounds = senderWindow.getBounds();
// const minHeight = senderWindow.getMinimumSize()[1];
// const maxHeight = senderWindow.getMaximumSize()[1];
// let adjustedHeight;
// if (maxHeight === 0) {
// adjustedHeight = Math.max(minHeight, targetHeight);
// } else {
// adjustedHeight = Math.max(minHeight, Math.min(maxHeight, targetHeight));
// }
// senderWindow.setSize(currentBounds.width, adjustedHeight, false);
// if (!wasResizable) {
// senderWindow.setResizable(false);
// }
// this.updateLayout();
// }
// };
// /**
// *
// * @param {object} [visibilityOverride] - { listen: true, ask: true }
// * @returns {{listen: {x:number, y:number}|null, ask: {x:number, y:number}|null}}
// */
// getTargetBoundsForFeatureWindows(visibilityOverride = {}) {
// const header = this.windowPool.get('header');
// if (!header?.getBounds) return {};
// const headerBounds = header.getBounds();
// const display = getCurrentDisplay(header);
// const { width: screenWidth, height: screenHeight } = display.workAreaSize;
// const { x: workAreaX, y: workAreaY } = display.workArea;
// const ask = this.windowPool.get('ask');
// const listen = this.windowPool.get('listen');
// const askVis = visibilityOverride.ask !== undefined ?
// visibilityOverride.ask :
// (ask && ask.isVisible() && !ask.isDestroyed());
// const listenVis = visibilityOverride.listen !== undefined ?
// visibilityOverride.listen :
// (listen && listen.isVisible() && !listen.isDestroyed());
// if (!askVis && !listenVis) return {};
// const PAD = 8;
// const headerTopRel = headerBounds.y - workAreaY;
// const headerBottomRel = headerTopRel + headerBounds.height;
// const headerCenterXRel = headerBounds.x - workAreaX + headerBounds.width / 2;
// const relativeX = headerCenterXRel / screenWidth;
// const relativeY = (headerBounds.y - workAreaY) / screenHeight;
// const strategy = this.determineLayoutStrategy(headerBounds, screenWidth, screenHeight, relativeX, relativeY, workAreaX, workAreaY);
// const askB = ask ? ask.getBounds() : null;
// const listenB = listen ? listen.getBounds() : null;
// const result = { listen: null, ask: null };
// if (askVis && listenVis) {
// let askXRel = headerCenterXRel - (askB.width / 2);
// let listenXRel = askXRel - listenB.width - PAD;
// if (listenXRel < PAD) {
// listenXRel = PAD;
// askXRel = listenXRel + listenB.width + PAD;
// }
// if (askXRel + askB.width > screenWidth - PAD) {
// askXRel = screenWidth - PAD - askB.width;
// listenXRel = askXRel - listenB.width - PAD;
// }
// // [수정] 'above'일 경우 하단 정렬, 'below'일 경우 상단 정렬
// if (strategy.primary === 'above') {
// const windowBottomAbs = headerBounds.y - PAD;
// const askY = windowBottomAbs - askB.height;
// const listenY = windowBottomAbs - listenB.height;
// result.ask = { x: Math.round(askXRel + workAreaX), y: Math.round(askY) };
// result.listen = { x: Math.round(listenXRel + workAreaX), y: Math.round(listenY) };
// } else { // 'below'
// const yPos = headerBottomRel + PAD;
// const yAbs = yPos + workAreaY;
// result.ask = { x: Math.round(askXRel + workAreaX), y: Math.round(yAbs) };
// result.listen = { x: Math.round(listenXRel + workAreaX), y: Math.round(yAbs) };
// }
// } else { // 한 창만 보일 때는 기존 로직 유지 (정상 동작 확인)
// const winB = askVis ? askB : listenB;
// let xRel = headerCenterXRel - winB.width / 2;
// xRel = Math.max(PAD, Math.min(screenWidth - winB.width - PAD, xRel));
// let yPos;
// if (strategy.primary === 'above') {
// const windowBottomRel = headerTopRel - PAD;
// yPos = windowBottomRel - winB.height;
// } else { // 'below'
// yPos = headerBottomRel + PAD;
// }
// const abs = { x: Math.round(xRel + workAreaX), y: Math.round(yPos + workAreaY) };
// if (askVis) result.ask = abs;
// if (listenVis) result.listen = abs;
// }
// return result;
// }
// positionWindows() {
// const header = this.windowPool.get('header');
// if (!header?.getBounds) return;
// const headerBounds = header.getBounds();
// const display = getCurrentDisplay(header);
// const { width: screenWidth, height: screenHeight } = display.workAreaSize;
// const { x: workAreaX, y: workAreaY } = display.workArea;
// const headerCenterX = headerBounds.x - workAreaX + headerBounds.width / 2;
// const headerCenterY = headerBounds.y - workAreaY + headerBounds.height / 2;
// const relativeX = headerCenterX / screenWidth;
// const relativeY = headerCenterY / screenHeight;
// const strategy = this.determineLayoutStrategy(headerBounds, screenWidth, screenHeight, relativeX, relativeY, workAreaX, workAreaY);
// this.positionFeatureWindows(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY);
// const settings = this.windowPool.get('settings');
// if (settings && !settings.isDestroyed() && settings.isVisible()) {
// const settingPos = this.calculateSettingsWindowPosition();
// if (settingPos) {
// const { width, height } = settings.getBounds();
// settings.setBounds({ x: settingPos.x, y: settingPos.y, width, height });
// }
// }
// }
/** /**
* *
@ -283,67 +64,6 @@ class WindowLayoutManager {
} }
// positionFeatureWindows(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY) {
// const ask = this.windowPool.get('ask');
// const listen = this.windowPool.get('listen');
// const askVisible = ask && ask.isVisible() && !ask.isDestroyed();
// const listenVisible = listen && listen.isVisible() && !listen.isDestroyed();
// if (!askVisible && !listenVisible) return;
// const PAD = 8;
// const headerTopRel = headerBounds.y - workAreaY;
// const headerBottomRel = headerTopRel + headerBounds.height;
// const headerCenterXRel = headerBounds.x - workAreaX + headerBounds.width / 2;
// let askBounds = askVisible ? ask.getBounds() : null;
// let listenBounds = listenVisible ? listen.getBounds() : null;
// if (askVisible && listenVisible) {
// let askXRel = headerCenterXRel - (askBounds.width / 2);
// let listenXRel = askXRel - listenBounds.width - PAD;
// if (listenXRel < PAD) {
// listenXRel = PAD;
// askXRel = listenXRel + listenBounds.width + PAD;
// }
// if (askXRel + askBounds.width > screenWidth - PAD) {
// askXRel = screenWidth - PAD - askBounds.width;
// listenXRel = askXRel - listenBounds.width - PAD;
// }
// // [수정] 'above'일 경우 하단 정렬, 'below'일 경우 상단 정렬
// if (strategy.primary === 'above') {
// const windowBottomAbs = headerBounds.y - PAD;
// const askY = windowBottomAbs - askBounds.height;
// const listenY = windowBottomAbs - listenBounds.height;
// ask.setBounds({ x: Math.round(askXRel + workAreaX), y: Math.round(askY), width: askBounds.width, height: askBounds.height });
// listen.setBounds({ x: Math.round(listenXRel + workAreaX), y: Math.round(listenY), width: listenBounds.width, height: listenBounds.height });
// } else { // 'below'
// const yPos = headerBottomRel + PAD;
// const yAbs = yPos + workAreaY;
// ask.setBounds({ x: Math.round(askXRel + workAreaX), y: Math.round(yAbs), width: askBounds.width, height: askBounds.height });
// listen.setBounds({ x: Math.round(listenXRel + workAreaX), y: Math.round(yAbs), width: listenBounds.width, height: listenBounds.height });
// }
// } else { // 한 창만 보일 때는 기존 로직 유지 (정상 동작 확인)
// const win = askVisible ? ask : listen;
// const winBounds = askVisible ? askBounds : listenBounds;
// let xRel = headerCenterXRel - winBounds.width / 2;
// xRel = Math.max(PAD, Math.min(screenWidth - winBounds.width - PAD, xRel));
// let yPos;
// if (strategy.primary === 'above') {
// const windowBottomRel = headerTopRel - PAD;
// yPos = windowBottomRel - winBounds.height;
// } else { // 'below'
// yPos = headerBottomRel + PAD;
// }
// const yAbs = yPos + workAreaY;
// win.setBounds({ x: Math.round(xRel + workAreaX), y: Math.round(yAbs), width: winBounds.width, height: winBounds.height });
// }
// }
/** /**
* @returns {{x: number, y: number} | null} * @returns {{x: number, y: number} | null}
@ -373,27 +93,6 @@ class WindowLayoutManager {
return { x: Math.round(clampedX), y: Math.round(clampedY) }; return { x: Math.round(clampedX), y: Math.round(clampedY) };
} }
// positionShortcutSettingsWindow() {
// const header = this.windowPool.get('header');
// const shortcutSettings = this.windowPool.get('shortcut-settings');
// if (!header || header.isDestroyed() || !shortcutSettings || shortcutSettings.isDestroyed()) {
// return;
// }
// const headerBounds = header.getBounds();
// const shortcutBounds = shortcutSettings.getBounds();
// const display = getCurrentDisplay(header);
// const { workArea } = display;
// let newX = Math.round(headerBounds.x + (headerBounds.width / 2) - (shortcutBounds.width / 2));
// let newY = Math.round(headerBounds.y);
// newX = Math.max(workArea.x, Math.min(newX, workArea.x + workArea.width - shortcutBounds.width));
// newY = Math.max(workArea.y, Math.min(newY, workArea.y + workArea.height - shortcutBounds.height));
// shortcutSettings.setBounds({ x: newX, y: newY, width: shortcutBounds.width, height: shortcutBounds.height });
// }
calculateHeaderResize(header, { width, height }) { calculateHeaderResize(header, { width, height }) {
if (!header) return null; if (!header) return null;
@ -429,12 +128,23 @@ class WindowLayoutManager {
} }
// 기존 getTargetBoundsForFeatureWindows를 이 함수로 대체합니다. // 기존 getTargetBoundsForFeatureWindows를 이 함수로 대체합니다.
calculateFeatureWindowLayout(visibility) { calculateFeatureWindowLayout(visibility, headerBoundsOverride = null) {
const header = this.windowPool.get('header'); const header = this.windowPool.get('header');
if (!header?.getBounds) return {}; const headerBounds = headerBoundsOverride || (header ? header.getBounds() : null);
if (!headerBounds) return {};
let display;
if (headerBoundsOverride) {
const boundsCenter = {
x: headerBounds.x + headerBounds.width / 2,
y: headerBounds.y + headerBounds.height / 2,
};
display = screen.getDisplayNearestPoint(boundsCenter);
} else {
display = getCurrentDisplay(header);
}
const headerBounds = header.getBounds();
const display = getCurrentDisplay(header);
const { width: screenWidth, height: screenHeight, x: workAreaX, y: workAreaY } = display.workArea; const { width: screenWidth, height: screenHeight, x: workAreaX, y: workAreaY } = display.workArea;
const ask = this.windowPool.get('ask'); const ask = this.windowPool.get('ask');
@ -456,6 +166,13 @@ class WindowLayoutManager {
const askB = askVis ? ask.getBounds() : null; const askB = askVis ? ask.getBounds() : null;
const listenB = listenVis ? listen.getBounds() : null; const listenB = listenVis ? listen.getBounds() : null;
if (askVis) {
console.log(`[Layout Debug] Ask Window Bounds: height=${askB.height}, width=${askB.width}`);
}
if (listenVis) {
console.log(`[Layout Debug] Listen Window Bounds: height=${listenB.height}, width=${listenB.width}`);
}
const layout = {}; const layout = {};

View File

@ -60,41 +60,6 @@ function updateChildWindowLayouts(animated = true) {
movementManager.animateLayout(newLayout, animated); movementManager.animateLayout(newLayout, animated);
} }
// /**
// * @param {BrowserWindow} win
// * @param {number} from
// * @param {number} to
// * @param {number} duration
// * @param {Function=} onComplete
// */
// function fadeWindow(win, from, to, duration = 250, onComplete) {
// if (!win || win.isDestroyed()) return;
// const FPS = 60;
// const steps = Math.max(1, Math.round(duration / (1000 / FPS)));
// let currentStep = 0;
// win.setOpacity(from);
// const timer = setInterval(() => {
// if (win.isDestroyed()) { clearInterval(timer); return; }
// currentStep += 1;
// const progress = currentStep / steps;
// const eased = progress < 1
// ? 1 - Math.pow(1 - progress, 3)
// : 1;
// win.setOpacity(from + (to - from) * eased);
// if (currentStep >= steps) {
// clearInterval(timer);
// win.setOpacity(to);
// onComplete && onComplete();
// }
// }, 1000 / FPS);
// }
const showSettingsWindow = () => { const showSettingsWindow = () => {
internalBridge.emit('window:requestVisibility', { name: 'settings', visible: true }); internalBridge.emit('window:requestVisibility', { name: 'settings', visible: true });
}; };
@ -120,7 +85,11 @@ const handleHeaderAnimationFinished = (state) => {
}; };
const getHeaderPosition = () => { const getHeaderPosition = () => {
internalBridge.emit('window:getHeaderPosition'); return new Promise((resolve) => {
internalBridge.emit('window:getHeaderPosition', (position) => {
resolve(position);
});
});
}; };
const moveHeaderTo = (newX, newY) => { const moveHeaderTo = (newX, newY) => {
@ -132,39 +101,6 @@ const adjustWindowHeight = (sender, targetHeight) => {
}; };
// function setupWindowController(windowPool, layoutManager, movementManager) {
// internalBridge.on('window:requestVisibility', ({ name, visible }) => {
// handleWindowVisibilityRequest(windowPool, layoutManager, movementManager, name, visible);
// });
// internalBridge.on('window:requestToggleAllWindowsVisibility', ({ targetVisibility }) => {
// changeAllWindowsVisibility(windowPool, targetVisibility);
// });
// internalBridge.on('window:moveToDisplay', ({ displayId }) => {
// movementManager.moveToDisplay(displayId);
// });
// internalBridge.on('window:moveToEdge', ({ direction }) => {
// movementManager.moveToEdge(direction);
// });
// internalBridge.on('window:moveStep', ({ direction }) => {
// movementManager.moveStep(direction);
// });
// internalBridge.on('window:resizeHeaderWindow', ({ width, height }) => {
// resizingHeaderWindow(layoutManager, movementManager, { width, height });
// });
// internalBridge.on('window:headerAnimationFinished', (state) => {
// handlingHeaderAnimationFinished(windowPool, layoutManager, state);
// });
// internalBridge.on('window:getHeaderPosition', () => {
// gettingHeaderPosition(layoutManager);
// });
// internalBridge.on('window:moveHeaderTo', ({ newX, newY }) => {
// movingHeaderTo(layoutManager, newX, newY);
// });
// internalBridge.on('window:adjustWindowHeight', ({ sender, targetHeight }) => {
// adjustingWindowHeight(layoutManager, sender, targetHeight);
// });
// }
function setupWindowController(windowPool, layoutManager, movementManager) { function setupWindowController(windowPool, layoutManager, movementManager) {
internalBridge.on('window:requestVisibility', ({ name, visible }) => { internalBridge.on('window:requestVisibility', ({ name, visible }) => {
handleWindowVisibilityRequest(windowPool, layoutManager, movementManager, name, visible); handleWindowVisibilityRequest(windowPool, layoutManager, movementManager, name, visible);
@ -193,15 +129,31 @@ function setupWindowController(windowPool, layoutManager, movementManager) {
}); });
} }
}); });
internalBridge.on('window:moveStep', ({ direction }) => { internalBridge.on('window:moveStep', ({ direction }) => {
const header = windowPool.get('header'); const header = windowPool.get('header');
if (header) { if (header) {
const newPosition = layoutManager.calculateStepMovePosition(header, direction); const newHeaderPosition = layoutManager.calculateStepMovePosition(header, direction);
movementManager.animateWindowPosition(header, newPosition, { if (!newHeaderPosition) return;
onComplete: () => updateChildWindowLayouts(true)
}); const futureHeaderBounds = { ...header.getBounds(), ...newHeaderPosition };
const visibleWindows = {};
const listenWin = windowPool.get('listen');
const askWin = windowPool.get('ask');
if (listenWin && !listenWin.isDestroyed() && listenWin.isVisible()) {
visibleWindows.listen = true;
}
if (askWin && !askWin.isDestroyed() && askWin.isVisible()) {
visibleWindows.ask = true;
}
const newChildLayout = layoutManager.calculateFeatureWindowLayout(visibleWindows, futureHeaderBounds);
movementManager.animateWindowPosition(header, newHeaderPosition);
movementManager.animateLayout(newChildLayout);
} }
}); });
internalBridge.on('window:resizeHeaderWindow', ({ width, height }) => { internalBridge.on('window:resizeHeaderWindow', ({ width, height }) => {
const header = windowPool.get('header'); const header = windowPool.get('header');
if (!header || movementManager.isAnimating) return; if (!header || movementManager.isAnimating) return;
@ -228,18 +180,19 @@ function setupWindowController(windowPool, layoutManager, movementManager) {
updateChildWindowLayouts(false); updateChildWindowLayouts(false);
} }
}); });
internalBridge.on('window:getHeaderPosition', () => { internalBridge.on('window:getHeaderPosition', (reply) => {
const header = windowPool.get('header'); const header = windowPool.get('header');
if (header) return header.getBounds(); if (header && !header.isDestroyed()) {
return { x: 0, y: 0 }; reply(header.getBounds());
} else {
reply({ x: 0, y: 0, width: 0, height: 0 });
}
}); });
internalBridge.on('window:moveHeaderTo', ({ newX, newY }) => { internalBridge.on('window:moveHeaderTo', ({ newX, newY }) => {
const header = windowPool.get('header'); const header = windowPool.get('header');
if (header) { if (header) {
const newPosition = layoutManager.calculateClampedPosition(header, { x: newX, y: newY }); const newPosition = layoutManager.calculateClampedPosition(header, { x: newX, y: newY });
movementManager.animateWindowPosition(header, newPosition, { header.setPosition(newPosition.x, newPosition.y);
onComplete: () => updateChildWindowLayouts(true)
});
} }
}); });
internalBridge.on('window:adjustWindowHeight', ({ sender, targetHeight }) => { internalBridge.on('window:adjustWindowHeight', ({ sender, targetHeight }) => {
@ -396,94 +349,6 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement
return; return;
} }
// if (name === 'listen' || name === 'ask') {
// const otherName = name === 'listen' ? 'ask' : 'listen';
// const otherWin = windowPool.get(otherName);
// const isOtherWinVisible = otherWin && !otherWin.isDestroyed() && otherWin.isVisible();
// const ANIM_OFFSET_X = 50;
// const ANIM_OFFSET_Y = 20;
// if (shouldBeVisible) {
// win.setOpacity(0);
// if (name === 'listen') {
// if (!isOtherWinVisible) {
// const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: false });
// if (!targets.listen) return;
// const startPos = { x: targets.listen.x - ANIM_OFFSET_X, y: targets.listen.y };
// win.setBounds(startPos);
// win.show();
// fadeWindow(win, 0, 1);
// movementManager.animateWindow(win, targets.listen.x, targets.listen.y);
// } else {
// const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: true });
// if (!targets.listen || !targets.ask) return;
// const startListenPos = { x: targets.listen.x - ANIM_OFFSET_X, y: targets.listen.y };
// win.setBounds(startListenPos);
// win.show();
// fadeWindow(win, 0, 1);
// movementManager.animateWindow(otherWin, targets.ask.x, targets.ask.y);
// movementManager.animateWindow(win, targets.listen.x, targets.listen.y);
// }
// } else if (name === 'ask') {
// if (!isOtherWinVisible) {
// const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: false, ask: true });
// if (!targets.ask) return;
// const startPos = { x: targets.ask.x, y: targets.ask.y - ANIM_OFFSET_Y };
// win.setBounds(startPos);
// win.show();
// fadeWindow(win, 0, 1);
// movementManager.animateWindow(win, targets.ask.x, targets.ask.y);
// } else {
// const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: true });
// if (!targets.listen || !targets.ask) return;
// const startAskPos = { x: targets.ask.x, y: targets.ask.y - ANIM_OFFSET_Y };
// win.setBounds(startAskPos);
// win.show();
// fadeWindow(win, 0, 1);
// movementManager.animateWindow(otherWin, targets.listen.x, targets.listen.y);
// movementManager.animateWindow(win, targets.ask.x, targets.ask.y);
// }
// }
// } else {
// const currentBounds = win.getBounds();
// fadeWindow(
// win, 1, 0, 250,
// () => win.hide()
// );
// if (name === 'listen') {
// if (!isOtherWinVisible) {
// const targetX = currentBounds.x - ANIM_OFFSET_X;
// movementManager.animateWindow(win, targetX, currentBounds.y);
// } else {
// const targetX = currentBounds.x - ANIM_OFFSET_X;
// movementManager.animateWindow(win, targetX, currentBounds.y);
// }
// } else if (name === 'ask') {
// if (!isOtherWinVisible) {
// const targetY = currentBounds.y - ANIM_OFFSET_Y;
// movementManager.animateWindow(win, currentBounds.x, targetY);
// } else {
// const targetAskY = currentBounds.y - ANIM_OFFSET_Y;
// movementManager.animateWindow(win, currentBounds.x, targetAskY);
// const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: false });
// if (targets.listen) {
// movementManager.animateWindow(otherWin, targets.listen.x, targets.listen.y);
// }
// }
// }
// }
// }
if (name === 'listen' || name === 'ask') { if (name === 'listen' || name === 'ask') {
const win = windowPool.get(name); const win = windowPool.get(name);
const otherName = name === 'listen' ? 'ask' : 'listen'; const otherName = name === 'listen' ? 'ask' : 'listen';
@ -578,7 +443,7 @@ function createFeatureWindows(header, namesToCreate) {
hasShadow: false, hasShadow: false,
skipTaskbar: true, skipTaskbar: true,
hiddenInMissionControl: true, hiddenInMissionControl: true,
resizable: true, resizable: false,
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
@ -831,11 +696,15 @@ function createWindows() {
} }
windowPool.set('header', header); windowPool.set('header', header);
layoutManager = new WindowLayoutManager(windowPool); layoutManager = new WindowLayoutManager(windowPool);
// movementManager = new SmoothMovementManager(windowPool, layoutManager);
movementManager = new SmoothMovementManager(windowPool); movementManager = new SmoothMovementManager(windowPool);
// header.on('moved', () => layoutManager.updateLayout());
header.on('moved', () => updateChildWindowLayouts(false)); header.on('moved', () => {
if (movementManager.isAnimating) {
return;
}
updateChildWindowLayouts(false);
});
header.webContents.once('dom-ready', () => { header.webContents.once('dom-ready', () => {
shortcutsService.initialize(windowPool); shortcutsService.initialize(windowPool);
@ -874,35 +743,11 @@ function createWindows() {
} }
}); });
// header.on('resize', () => {
// console.log('[WindowManager] Header resize event triggered');
// layoutManager.updateLayout();
// });
header.on('resize', () => updateChildWindowLayouts(false)); header.on('resize', () => updateChildWindowLayouts(false));
return windowPool; return windowPool;
} }
// function setupIpcHandlers(movementManager, layoutManager) {
// // quit-application handler moved to windowBridge.js to avoid duplication
// screen.on('display-added', (event, newDisplay) => {
// console.log('[Display] New display added:', newDisplay.id);
// });
// screen.on('display-removed', (event, oldDisplay) => {
// console.log('[Display] Display removed:', oldDisplay.id);
// const header = windowPool.get('header');
// if (header && getCurrentDisplay(header).id === oldDisplay.id) {
// const primaryDisplay = screen.getPrimaryDisplay();
// movementManager.moveToDisplay(primaryDisplay.id);
// }
// });
// screen.on('display-metrics-changed', (event, display, changedMetrics) => {
// // console.log('[Display] Display metrics changed:', display.id, changedMetrics);
// layoutManager.updateLayout();
// });
// }
function setupIpcHandlers(windowPool, layoutManager) { function setupIpcHandlers(windowPool, layoutManager) {
screen.on('display-added', (event, newDisplay) => { screen.on('display-added', (event, newDisplay) => {
@ -944,41 +789,6 @@ const handleHeaderStateChanged = (state) => {
}; };
// const resizingHeaderWindow = (layoutManager, movementManager, { width, height }) => {
// if (movementManager.isAnimating) {
// console.log('[WindowManager] Skipping resize during animation');
// return { success: false, error: 'Cannot resize during animation' };
// }
// return layoutManager.resizeHeaderWindow({ width, height });
// };
// const handlingHeaderAnimationFinished = (windowPool, layoutManager, state) => {
// const header = windowPool.get('header');
// if (!header || header.isDestroyed()) return;
// if (state === 'hidden') {
// header.hide();
// console.log('[WindowManager] Header hidden after animation.');
// } else if (state === 'visible') {
// console.log('[WindowManager] Header shown after animation.');
// layoutManager.updateLayout();
// }
// };
// const gettingHeaderPosition = (layoutManager) => {
// return layoutManager.getHeaderPosition();
// };
// const movingHeaderTo = (layoutManager, newX, newY) => {
// layoutManager.moveHeaderTo(newX, newY);
// };
// const adjustingWindowHeight = (layoutManager, sender, targetHeight) => {
// layoutManager.adjustWindowHeight(sender, targetHeight);
// };
module.exports = { module.exports = {
createWindows, createWindows,
windowPool, windowPool,