diff --git a/src/window/smoothMovementManager.js b/src/window/smoothMovementManager.js index 044fb00..d1ed190 100644 --- a/src/window/smoothMovementManager.js +++ b/src/window/smoothMovementManager.js @@ -19,9 +19,10 @@ function getDisplayById(displayId) { } class SmoothMovementManager { - constructor(windowPool, layoutManager) { + // constructor(windowPool, layoutManager) { + constructor(windowPool) { this.windowPool = windowPool; - this.layoutManager = layoutManager; + // this.layoutManager = layoutManager; this.stepSize = 80; this.animationDuration = 300; this.headerPosition = { x: 0, y: 0 }; @@ -51,90 +52,90 @@ class SmoothMovementManager { return true; } - moveToDisplay(displayId) { - const header = this.windowPool.get('header'); - if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return; + // 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 targetDisplay = getDisplayById(displayId); + // if (!targetDisplay) return; - const currentBounds = header.getBounds(); - const currentDisplay = getCurrentDisplay(header); + // const currentBounds = header.getBounds(); + // const currentDisplay = getCurrentDisplay(header); - if (currentDisplay.id === targetDisplay.id) return; + // 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 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)); + // 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; - } + // 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; + // 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; + // 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})`); + // console.log(`[MovementManager] Moving ${direction} from (${targetX}, ${targetY})`); - const windowSize = { - width: currentBounds.width, - height: currentBounds.height - }; + // 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; - } + // 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; + // // 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; + // // 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 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}`); - } + // // 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}`); + // 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; - } + // // 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); - } + // this.animateToPosition(header, clampedX, clampedY, windowSize); + // } /** * [수정됨] 창을 목표 지점으로 부드럽게 애니메이션합니다. @@ -186,100 +187,190 @@ class SmoothMovementManager { step(); } - animateToPosition(header, targetX, targetY, windowSize) { - if (!this._isWindowValid(header)) return; + // 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(); + // 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; + // 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(); + 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._isWindowValid(win)) { + if (options.onComplete) options.onComplete(); 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 = 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; } - - if (!this._isWindowValid(header)) return; - const { width, height } = windowSize || header.getBounds(); - header.setBounds({ - x: Math.round(currentX), - y: Math.round(currentY), - width, - height - }); - + + 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) { - this.animationFrameId = setTimeout(animate, 8); + setTimeout(step, 8); } else { - this.animationFrameId = null; + win.setBounds(targetBounds); 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(); + if (options.onComplete) options.onComplete(); } }; - animate(); + step(); } - - 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; + + 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); + } + } } - - 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(); } destroy() { diff --git a/src/window/windowLayoutManager.js b/src/window/windowLayoutManager.js index f4ba051..690ce00 100644 --- a/src/window/windowLayoutManager.js +++ b/src/window/windowLayoutManager.js @@ -27,15 +27,15 @@ class WindowLayoutManager { this.PADDING = 80; } - updateLayout() { - if (this.isUpdating) return; - this.isUpdating = true; + // updateLayout() { + // if (this.isUpdating) return; + // this.isUpdating = true; - setImmediate(() => { - this.positionWindows(); - this.isUpdating = false; - }); - } + // setImmediate(() => { + // this.positionWindows(); + // this.isUpdating = false; + // }); + // } getHeaderPosition = () => { const header = this.windowPool.get('header'); @@ -46,215 +46,215 @@ 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}`); + // 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})`); + // 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 }; - } + // 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 wasResizable = header.isResizable(); + // if (!wasResizable) { + // header.setResizable(true); + // } - const centerX = currentBounds.x + currentBounds.width / 2; - const newX = Math.round(centerX - width / 2); + // 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 display = getCurrentDisplay(header); + // const { x: workAreaX, width: workAreaWidth } = display.workArea; - const clampedX = Math.max(workAreaX, Math.min(workAreaX + workAreaWidth - width, newX)); + // const clampedX = Math.max(workAreaX, Math.min(workAreaX + workAreaWidth - width, newX)); - header.setBounds({ x: clampedX, y: currentBounds.y, width, height }); + // header.setBounds({ x: clampedX, y: currentBounds.y, width, height }); - if (!wasResizable) { - header.setResizable(false); - } + // if (!wasResizable) { + // header.setResizable(false); + // } - this.updateLayout(); + // this.updateLayout(); - return { success: true }; - } - return { success: false, error: 'Header window not found' }; - }; + // 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(); + // 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; + // let clampedX = newX; + // let clampedY = newY; - if (newX < workAreaX) { - clampedX = workAreaX; - } else if (newX + headerBounds.width > workAreaX + width) { - clampedX = workAreaX + width - headerBounds.width; - } + // 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; - } + // if (newY < workAreaY) { + // clampedY = workAreaY; + // } else if (newY + headerBounds.height > workAreaY + height) { + // clampedY = workAreaY + height - headerBounds.height; + // } - header.setPosition(clampedX, clampedY, false); - this.updateLayout(); - } - }; + // 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); - } + // 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]; + // 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)); - } + // 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); + // senderWindow.setSize(currentBounds.width, adjustedHeight, false); - if (!wasResizable) { - senderWindow.setResizable(false); - } + // if (!wasResizable) { + // senderWindow.setResizable(false); + // } - this.updateLayout(); - } - }; + // 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 {}; + // /** + // * + // * @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 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 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()); + // 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 {}; + // 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 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 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 askB = ask ? ask.getBounds() : null; + // const listenB = listen ? listen.getBounds() : null; - const result = { listen: null, ask: null }; + // const result = { listen: null, ask: null }; - if (askVis && listenVis) { - let askXRel = headerCenterXRel - (askB.width / 2); - let listenXRel = askXRel - listenB.width - PAD; + // 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; - } + // 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) }; - } + // // [수정] '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)); + // } 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; - } + // 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; - } + // 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; + // 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 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 headerCenterX = headerBounds.x - workAreaX + headerBounds.width / 2; + // const headerCenterY = headerBounds.y - workAreaY + headerBounds.height / 2; - const relativeX = headerCenterX / screenWidth; - const relativeY = headerCenterY / screenHeight; + // const relativeX = headerCenterX / screenWidth; + // const relativeY = headerCenterY / screenHeight; - const strategy = this.determineLayoutStrategy(headerBounds, screenWidth, screenHeight, relativeX, relativeY, workAreaX, workAreaY); + // 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 }); - } - } - } + // 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 +283,67 @@ 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(); + // 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; + // 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; + // 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; + // 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 (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; - } + // 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 }); - } + // // [수정] '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)); + // } 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; + // 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 }); - } - } + // win.setBounds({ x: Math.round(xRel + workAreaX), y: Math.round(yAbs), width: winBounds.width, height: winBounds.height }); + // } + // } /** * @returns {{x: number, y: number} | null} @@ -373,26 +373,208 @@ class WindowLayoutManager { return { x: Math.round(clampedX), y: Math.round(clampedY) }; } - positionShortcutSettingsWindow() { + // 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; + const currentBounds = header.getBounds(); + 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)); + return { x: clampedX, y: currentBounds.y, width, height }; + } + + calculateClampedPosition(header, { x: newX, y: newY }) { + if (!header) return null; + const targetDisplay = screen.getDisplayNearestPoint({ x: newX, y: newY }); + const { x: workAreaX, y: workAreaY, width, height } = targetDisplay.workArea; + const headerBounds = header.getBounds(); + const clampedX = Math.max(workAreaX, Math.min(newX, workAreaX + width - headerBounds.width)); + const clampedY = Math.max(workAreaY, Math.min(newY, workAreaY + height - headerBounds.height)); + return { x: clampedX, y: clampedY }; + } + + calculateWindowHeightAdjustment(senderWindow, targetHeight) { + if (!senderWindow) return null; + const currentBounds = senderWindow.getBounds(); + const minHeight = senderWindow.getMinimumSize()[1]; + const maxHeight = senderWindow.getMaximumSize()[1]; + let adjustedHeight = Math.max(minHeight, targetHeight); + if (maxHeight > 0) { + adjustedHeight = Math.min(maxHeight, adjustedHeight); + } + return { ...currentBounds, height: adjustedHeight }; + } + + // 기존 getTargetBoundsForFeatureWindows를 이 함수로 대체합니다. + calculateFeatureWindowLayout(visibility) { + const header = this.windowPool.get('header'); + if (!header?.getBounds) return {}; + + 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'); + const listen = this.windowPool.get('listen'); + + const askVis = visibility.ask && ask && !ask.isDestroyed(); + const listenVis = visibility.listen && listen && !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 = askVis ? ask.getBounds() : null; + const listenB = listenVis ? listen.getBounds() : null; + + const layout = {}; + + 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; + } + + if (strategy.primary === 'above') { + const windowBottomAbs = headerBounds.y - PAD; + layout.ask = { x: Math.round(askXRel + workAreaX), y: Math.round(windowBottomAbs - askB.height), width: askB.width, height: askB.height }; + layout.listen = { x: Math.round(listenXRel + workAreaX), y: Math.round(windowBottomAbs - listenB.height), width: listenB.width, height: listenB.height }; + } else { // 'below' + const yAbs = headerBounds.y + headerBounds.height + PAD; + layout.ask = { x: Math.round(askXRel + workAreaX), y: Math.round(yAbs), width: askB.width, height: askB.height }; + layout.listen = { x: Math.round(listenXRel + workAreaX), y: Math.round(yAbs), width: listenB.width, height: listenB.height }; + } + } else { // Single window + const winName = askVis ? 'ask' : 'listen'; + const winB = askVis ? askB : listenB; + if (!winB) return {}; + + let xRel = headerCenterXRel - winB.width / 2; + xRel = Math.max(PAD, Math.min(screenWidth - winB.width - PAD, xRel)); + + let yPos; + if (strategy.primary === 'above') { + yPos = (headerBounds.y - workAreaY) - PAD - winB.height; + } else { // 'below' + yPos = (headerBounds.y - workAreaY) + headerBounds.height + PAD; + } + + layout[winName] = { x: Math.round(xRel + workAreaX), y: Math.round(yPos + workAreaY), width: winB.width, height: winB.height }; + } + return layout; + } + + calculateShortcutSettingsWindowPosition() { const header = this.windowPool.get('header'); const shortcutSettings = this.windowPool.get('shortcut-settings'); - - if (!header || header.isDestroyed() || !shortcutSettings || shortcutSettings.isDestroyed()) { - return; - } - + if (!header || !shortcutSettings) return null; + const headerBounds = header.getBounds(); const shortcutBounds = shortcutSettings.getBounds(); - const display = getCurrentDisplay(header); - const { workArea } = display; - + const { workArea } = getCurrentDisplay(header); + 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)); + + return { x: newX, y: newY, width: shortcutBounds.width, height: shortcutBounds.height }; + } - shortcutSettings.setBounds({ x: newX, y: newY, width: shortcutBounds.width, height: shortcutBounds.height }); + calculateStepMovePosition(header, direction) { + if (!header) return null; + const currentBounds = header.getBounds(); + const stepSize = 80; // 이동 간격 + let targetX = currentBounds.x; + let targetY = currentBounds.y; + + switch (direction) { + case 'left': targetX -= stepSize; break; + case 'right': targetX += stepSize; break; + case 'up': targetY -= stepSize; break; + case 'down': targetY += stepSize; break; + } + + return this.calculateClampedPosition(header, { x: targetX, y: targetY }); + } + + calculateEdgePosition(header, direction) { + if (!header) return null; + const display = getCurrentDisplay(header); + const { workArea } = display; + const currentBounds = header.getBounds(); + + let targetX = currentBounds.x; + let targetY = currentBounds.y; + + switch (direction) { + case 'left': targetX = workArea.x; break; + case 'right': targetX = workArea.x + workArea.width - currentBounds.width; break; + case 'up': targetY = workArea.y; break; + case 'down': targetY = workArea.y + workArea.height - currentBounds.height; break; + } + return { x: targetX, y: targetY }; + } + + calculateNewPositionForDisplay(window, targetDisplayId) { + if (!window) return null; + + const targetDisplay = screen.getAllDisplays().find(d => d.id === targetDisplayId); + if (!targetDisplay) return null; + + const currentBounds = window.getBounds(); + const currentDisplay = getCurrentDisplay(window); + + if (currentDisplay.id === targetDisplay.id) return { x: currentBounds.x, y: currentBounds.y }; + + const relativeX = (currentBounds.x - currentDisplay.workArea.x) / currentDisplay.workArea.width; + const relativeY = (currentBounds.y - currentDisplay.workArea.y) / currentDisplay.workArea.height; + + const targetX = targetDisplay.workArea.x + targetDisplay.workArea.width * relativeX; + const targetY = targetDisplay.workArea.y + targetDisplay.workArea.height * relativeY; + + const clampedX = Math.max(targetDisplay.workArea.x, Math.min(targetX, targetDisplay.workArea.x + targetDisplay.workArea.width - currentBounds.width)); + const clampedY = Math.max(targetDisplay.workArea.y, Math.min(targetY, targetDisplay.workArea.y + targetDisplay.workArea.height - currentBounds.height)); + + return { x: Math.round(clampedX), y: Math.round(clampedY) }; } /** diff --git a/src/window/windowManager.js b/src/window/windowManager.js index f57dcd5..3ef5892 100644 --- a/src/window/windowManager.js +++ b/src/window/windowManager.js @@ -40,41 +40,61 @@ let settingsHideTimer = null; let layoutManager = null; let movementManager = null; -/** - * @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; +function updateChildWindowLayouts(animated = true) { + if (movementManager.isAnimating) return; - 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(); + const visibleWindows = {}; + const listenWin = windowPool.get('listen'); + const askWin = windowPool.get('ask'); + if (listenWin && !listenWin.isDestroyed() && listenWin.isVisible()) { + visibleWindows.listen = true; } - }, 1000 / FPS); + if (askWin && !askWin.isDestroyed() && askWin.isVisible()) { + visibleWindows.ask = true; + } + + if (Object.keys(visibleWindows).length === 0) return; + + const newLayout = layoutManager.calculateFeatureWindowLayout(visibleWindows); + 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 }); }; @@ -112,6 +132,39 @@ 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); @@ -120,28 +173,90 @@ function setupWindowController(windowPool, layoutManager, movementManager) { changeAllWindowsVisibility(windowPool, targetVisibility); }); internalBridge.on('window:moveToDisplay', ({ displayId }) => { - movementManager.moveToDisplay(displayId); + // movementManager.moveToDisplay(displayId); + const header = windowPool.get('header'); + if (header) { + const newPosition = layoutManager.calculateNewPositionForDisplay(header, displayId); + if (newPosition) { + movementManager.animateWindowPosition(header, newPosition, { + onComplete: () => updateChildWindowLayouts(true) + }); + } + } }); internalBridge.on('window:moveToEdge', ({ direction }) => { - movementManager.moveToEdge(direction); + const header = windowPool.get('header'); + if (header) { + const newPosition = layoutManager.calculateEdgePosition(header, direction); + movementManager.animateWindowPosition(header, newPosition, { + onComplete: () => updateChildWindowLayouts(true) + }); + } }); internalBridge.on('window:moveStep', ({ direction }) => { - movementManager.moveStep(direction); + const header = windowPool.get('header'); + if (header) { + const newPosition = layoutManager.calculateStepMovePosition(header, direction); + movementManager.animateWindowPosition(header, newPosition, { + onComplete: () => updateChildWindowLayouts(true) + }); + } }); internalBridge.on('window:resizeHeaderWindow', ({ width, height }) => { - resizingHeaderWindow(layoutManager, movementManager, { width, height }); + const header = windowPool.get('header'); + if (!header || movementManager.isAnimating) return; + + const newHeaderBounds = layoutManager.calculateHeaderResize(header, { width, height }); + + const wasResizable = header.isResizable(); + if (!wasResizable) header.setResizable(true); + + movementManager.animateWindowBounds(header, newHeaderBounds, { + onComplete: () => { + if (!wasResizable) header.setResizable(false); + updateChildWindowLayouts(true); + } + }); }); internalBridge.on('window:headerAnimationFinished', (state) => { - handlingHeaderAnimationFinished(windowPool, layoutManager, state); + const header = windowPool.get('header'); + if (!header || header.isDestroyed()) return; + + if (state === 'hidden') { + header.hide(); + } else if (state === 'visible') { + updateChildWindowLayouts(false); + } }); internalBridge.on('window:getHeaderPosition', () => { - gettingHeaderPosition(layoutManager); + const header = windowPool.get('header'); + if (header) return header.getBounds(); + return { x: 0, y: 0 }; }); internalBridge.on('window:moveHeaderTo', ({ newX, newY }) => { - movingHeaderTo(layoutManager, 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) + }); + } }); internalBridge.on('window:adjustWindowHeight', ({ sender, targetHeight }) => { - adjustingWindowHeight(layoutManager, sender, targetHeight); + const senderWindow = windowPool.get(sender); + if (senderWindow) { + const newBounds = layoutManager.calculateWindowHeightAdjustment(senderWindow, targetHeight); + + const wasResizable = senderWindow.isResizable(); + if (!wasResizable) senderWindow.setResizable(true); + + movementManager.animateWindowBounds(senderWindow, newBounds, { + onComplete: () => { + if (!wasResizable) senderWindow.setResizable(false); + updateChildWindowLayouts(true); + } + }); + } }); } @@ -257,7 +372,10 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement if (name === 'shortcut-settings') { if (shouldBeVisible) { - layoutManager.positionShortcutSettingsWindow(); + // layoutManager.positionShortcutSettingsWindow(); + const newBounds = layoutManager.calculateShortcutSettingsWindowPosition(); + if (newBounds) win.setBounds(newBounds); + if (process.platform === 'darwin') { win.setAlwaysOnTop(true, 'screen-saver'); } else { @@ -278,92 +396,144 @@ 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'; const otherWin = windowPool.get(otherName); const isOtherWinVisible = otherWin && !otherWin.isDestroyed() && otherWin.isVisible(); - - const ANIM_OFFSET_X = 50; + + const ANIM_OFFSET_X = 50; const ANIM_OFFSET_Y = 20; + const finalVisibility = { + listen: (name === 'listen' && shouldBeVisible) || (otherName === 'listen' && isOtherWinVisible), + ask: (name === 'ask' && shouldBeVisible) || (otherName === 'ask' && isOtherWinVisible), + }; + if (!shouldBeVisible) { + finalVisibility[name] = false; + } + + const targetLayout = layoutManager.calculateFeatureWindowLayout(finalVisibility); + if (shouldBeVisible) { + if (!win) return; + const targetBounds = targetLayout[name]; + if (!targetBounds) return; + + const startPos = { ...targetBounds }; + if (name === 'listen') startPos.x -= ANIM_OFFSET_X; + else if (name === 'ask') startPos.y -= ANIM_OFFSET_Y; + win.setOpacity(0); + win.setBounds(startPos); + win.show(); - if (name === 'listen') { - if (!isOtherWinVisible) { - const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: false }); - if (!targets.listen) return; + movementManager.fade(win, { to: 1 }); + movementManager.animateLayout(targetLayout); - 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); + if (!win || !win.isVisible()) return; - const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: false }); - if (targets.listen) { - movementManager.animateWindow(otherWin, targets.listen.x, targets.listen.y); - } - } - } + const currentBounds = win.getBounds(); + const targetPos = { ...currentBounds }; + if (name === 'listen') targetPos.x -= ANIM_OFFSET_X; + else if (name === 'ask') targetPos.y -= ANIM_OFFSET_Y; + + movementManager.fade(win, { to: 0, onComplete: () => win.hide() }); + movementManager.animateWindowPosition(win, targetPos); + + // 다른 창들도 새 레이아웃으로 애니메이션 + const otherWindowsLayout = { ...targetLayout }; + delete otherWindowsLayout[name]; + movementManager.animateLayout(otherWindowsLayout); } } } @@ -661,16 +831,18 @@ function createWindows() { } windowPool.set('header', header); layoutManager = new WindowLayoutManager(windowPool); - movementManager = new SmoothMovementManager(windowPool, layoutManager); + // movementManager = new SmoothMovementManager(windowPool, layoutManager); + movementManager = new SmoothMovementManager(windowPool); - header.on('moved', () => layoutManager.updateLayout()); + // header.on('moved', () => layoutManager.updateLayout()); + header.on('moved', () => updateChildWindowLayouts(false)); header.webContents.once('dom-ready', () => { shortcutsService.initialize(windowPool); shortcutsService.registerShortcuts(); }); - setupIpcHandlers(movementManager, layoutManager); + setupIpcHandlers(windowPool, layoutManager); setupWindowController(windowPool, layoutManager, movementManager); if (currentHeaderState === 'main') { @@ -702,16 +874,37 @@ function createWindows() { } }); - header.on('resize', () => { - console.log('[WindowManager] Header resize event triggered'); - layoutManager.updateLayout(); - }); + // 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 +// 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) => { console.log('[Display] New display added:', newDisplay.id); }); @@ -719,15 +912,21 @@ function setupIpcHandlers(movementManager, layoutManager) { 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); + const newPosition = layoutManager.calculateNewPositionForDisplay(header, primaryDisplay.id); + if (newPosition) { + // 복구 상황이므로 애니메이션 없이 즉시 이동 + header.setPosition(newPosition.x, newPosition.y, false); + updateChildWindowLayouts(false); + } } }); screen.on('display-metrics-changed', (event, display, changedMetrics) => { - // console.log('[Display] Display metrics changed:', display.id, changedMetrics); - layoutManager.updateLayout(); + // 레이아웃 업데이트 함수를 새 버전으로 호출 + updateChildWindowLayouts(false); }); } @@ -745,39 +944,39 @@ 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' }; - } +// 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 }); -}; +// return layoutManager.resizeHeaderWindow({ width, height }); +// }; -const handlingHeaderAnimationFinished = (windowPool, layoutManager, state) => { - const header = windowPool.get('header'); - if (!header || header.isDestroyed()) return; +// 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(); - } -}; +// 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 gettingHeaderPosition = (layoutManager) => { +// return layoutManager.getHeaderPosition(); +// }; -const movingHeaderTo = (layoutManager, newX, newY) => { - layoutManager.moveHeaderTo(newX, newY); -}; +// const movingHeaderTo = (layoutManager, newX, newY) => { +// layoutManager.moveHeaderTo(newX, newY); +// }; -const adjustingWindowHeight = (layoutManager, sender, targetHeight) => { - layoutManager.adjustWindowHeight(sender, targetHeight); -}; +// const adjustingWindowHeight = (layoutManager, sender, targetHeight) => { +// layoutManager.adjustWindowHeight(sender, targetHeight); +// }; module.exports = {