diff --git a/src/window/smoothMovementManager.js b/src/window/smoothMovementManager.js index d1ed190..63a8ab8 100644 --- a/src/window/smoothMovementManager.js +++ b/src/window/smoothMovementManager.js @@ -1,28 +1,8 @@ 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 { - // constructor(windowPool, layoutManager) { constructor(windowPool) { this.windowPool = windowPool; - // this.layoutManager = layoutManager; this.stepSize = 80; this.animationDuration = 300; this.headerPosition = { x: 0, y: 0 }; @@ -31,6 +11,8 @@ class SmoothMovementManager { this.lastVisiblePosition = null; this.currentDisplayId = null; this.animationFrameId = null; + + this.animationTimers = new Map(); } /** @@ -39,114 +21,25 @@ class SmoothMovementManager { */ _isWindowValid(win) { if (!win || win.isDestroyed()) { - if (this.isAnimating) { - console.warn('[MovementManager] Window destroyed mid-animation. Halting.'); - this.isAnimating = false; - if (this.animationFrameId) { - clearTimeout(this.animationFrameId); - this.animationFrameId = null; - } + // 해당 창의 타이머가 있으면 정리 + if (this.animationTimers.has(win)) { + clearTimeout(this.animationTimers.get(win)); + this.animationTimers.delete(win); } return false; } 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 {number} targetX - 목표 X 좌표 - * @param {number} targetY - 목표 Y 좌표 - * @param {object} [options] - 추가 옵션 - * @param {object} [options.sizeOverride] - 애니메이션 중 사용할 창 크기 - * @param {function} [options.onComplete] - 애니메이션 완료 후 실행할 콜백 - * @param {number} [options.duration] - 애니메이션 지속 시간 (ms) + * + * @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)) { @@ -161,7 +54,6 @@ class SmoothMovementManager { const { width, height } = sizeOverride || start; const step = () => { - // 애니메이션 중간에 창이 파괴될 경우 콜백을 실행하고 중단 if (!this._isWindowValid(win)) { if (onComplete) onComplete(); return; @@ -175,114 +67,17 @@ class SmoothMovementManager { win.setBounds({ x: Math.round(x), y: Math.round(y), width, height }); if (p < 1) { - setTimeout(step, 8); // requestAnimationFrame 대신 setTimeout으로 간결하게 처리 + setTimeout(step, 8); } else { - // 애니메이션 종료 - this.layoutManager.updateLayout(); // 레이아웃 재정렬 + this.layoutManager.updateLayout(); if (onComplete) { - onComplete(); // 완료 콜백 실행 + onComplete(); } } }; 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 }) { if (!this._isWindowValid(win)) { if (onComplete) onComplete(); @@ -310,18 +105,23 @@ class SmoothMovementManager { } 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)) { - this.isAnimating = false; if (options.onComplete) options.onComplete(); return; } @@ -338,10 +138,16 @@ class SmoothMovementManager { win.setBounds(newBounds); if (progress < 1) { - setTimeout(step, 8); + const timerId = setTimeout(step, 8); + this.animationTimers.set(win, timerId); } else { win.setBounds(targetBounds); - this.isAnimating = false; + this.animationTimers.delete(win); + + if (this.animationTimers.size === 0) { + this.isAnimating = false; + } + if (options.onComplete) options.onComplete(); } }; diff --git a/src/window/windowLayoutManager.js b/src/window/windowLayoutManager.js index 690ce00..07965bb 100644 --- a/src/window/windowLayoutManager.js +++ b/src/window/windowLayoutManager.js @@ -27,16 +27,6 @@ class WindowLayoutManager { this.PADDING = 80; } - // updateLayout() { - // if (this.isUpdating) return; - // this.isUpdating = true; - - // setImmediate(() => { - // this.positionWindows(); - // this.isUpdating = false; - // }); - // } - getHeaderPosition = () => { const header = this.windowPool.get('header'); if (header) { @@ -46,215 +36,6 @@ class WindowLayoutManager { 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} @@ -373,27 +93,6 @@ class WindowLayoutManager { 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 }) { if (!header) return null; @@ -429,12 +128,23 @@ class WindowLayoutManager { } // 기존 getTargetBoundsForFeatureWindows를 이 함수로 대체합니다. - calculateFeatureWindowLayout(visibility) { + calculateFeatureWindowLayout(visibility, headerBoundsOverride = null) { 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 ask = this.windowPool.get('ask'); @@ -456,6 +166,13 @@ class WindowLayoutManager { const askB = askVis ? ask.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 = {}; diff --git a/src/window/windowManager.js b/src/window/windowManager.js index 3ef5892..5e29ec5 100644 --- a/src/window/windowManager.js +++ b/src/window/windowManager.js @@ -60,41 +60,6 @@ function updateChildWindowLayouts(animated = true) { 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 = () => { internalBridge.emit('window:requestVisibility', { name: 'settings', visible: true }); }; @@ -120,7 +85,11 @@ const handleHeaderAnimationFinished = (state) => { }; const getHeaderPosition = () => { - internalBridge.emit('window:getHeaderPosition'); + return new Promise((resolve) => { + internalBridge.emit('window:getHeaderPosition', (position) => { + resolve(position); + }); + }); }; 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) { internalBridge.on('window:requestVisibility', ({ name, visible }) => { handleWindowVisibilityRequest(windowPool, layoutManager, movementManager, name, visible); @@ -193,15 +129,31 @@ function setupWindowController(windowPool, layoutManager, movementManager) { }); } }); + internalBridge.on('window:moveStep', ({ direction }) => { const header = windowPool.get('header'); - if (header) { - const newPosition = layoutManager.calculateStepMovePosition(header, direction); - movementManager.animateWindowPosition(header, newPosition, { - onComplete: () => updateChildWindowLayouts(true) - }); + if (header) { + const newHeaderPosition = layoutManager.calculateStepMovePosition(header, direction); + if (!newHeaderPosition) return; + + 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 }) => { const header = windowPool.get('header'); if (!header || movementManager.isAnimating) return; @@ -228,18 +180,19 @@ function setupWindowController(windowPool, layoutManager, movementManager) { updateChildWindowLayouts(false); } }); - internalBridge.on('window:getHeaderPosition', () => { + internalBridge.on('window:getHeaderPosition', (reply) => { const header = windowPool.get('header'); - if (header) return header.getBounds(); - return { x: 0, y: 0 }; + if (header && !header.isDestroyed()) { + reply(header.getBounds()); + } else { + reply({ x: 0, y: 0, width: 0, height: 0 }); + } }); internalBridge.on('window:moveHeaderTo', ({ newX, newY }) => { const header = windowPool.get('header'); if (header) { const newPosition = layoutManager.calculateClampedPosition(header, { x: newX, y: newY }); - movementManager.animateWindowPosition(header, newPosition, { - onComplete: () => updateChildWindowLayouts(true) - }); + header.setPosition(newPosition.x, newPosition.y); } }); internalBridge.on('window:adjustWindowHeight', ({ sender, targetHeight }) => { @@ -396,94 +349,6 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement 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') { const win = windowPool.get(name); const otherName = name === 'listen' ? 'ask' : 'listen'; @@ -578,7 +443,7 @@ function createFeatureWindows(header, namesToCreate) { hasShadow: false, skipTaskbar: true, hiddenInMissionControl: true, - resizable: true, + resizable: false, webPreferences: { nodeIntegration: false, contextIsolation: true, @@ -831,11 +696,15 @@ function createWindows() { } windowPool.set('header', header); layoutManager = new WindowLayoutManager(windowPool); - // movementManager = new SmoothMovementManager(windowPool, layoutManager); 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', () => { 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)); 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) { 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 = { createWindows, windowPool,