From 698473007a91c74ac5f201c001ad599a1a747362 Mon Sep 17 00:00:00 2001 From: sanio Date: Tue, 15 Jul 2025 14:59:14 +0900 Subject: [PATCH 1/4] delete movementmanager dependency in shortcutsservice --- src/features/shortcuts/shortcutsService.js | 18 ++++++------- src/ui/settings/ShortCutSettingsView.js | 2 ++ src/window/windowManager.js | 30 +++++++++++++--------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/features/shortcuts/shortcutsService.js b/src/features/shortcuts/shortcutsService.js index 44ad6e7..ec2cc2c 100644 --- a/src/features/shortcuts/shortcutsService.js +++ b/src/features/shortcuts/shortcutsService.js @@ -8,13 +8,11 @@ class ShortcutsService { constructor() { this.lastVisibleWindows = new Set(['header']); this.mouseEventsIgnored = false; - this.movementManager = null; this.windowPool = null; this.allWindowVisibility = true; } - initialize(movementManager, windowPool) { - this.movementManager = movementManager; + initialize(windowPool) { this.windowPool = windowPool; internalBridge.on('reregister-shortcuts', () => { console.log('[ShortcutsService] Reregistering shortcuts due to header state change.'); @@ -138,7 +136,7 @@ class ShortcutsService { } async registerShortcuts(registerOnlyToggleVisibility = false) { - if (!this.movementManager || !this.windowPool) { + if (!this.windowPool) { console.error('[Shortcuts] Service not initialized. Cannot register shortcuts.'); return; } @@ -179,7 +177,7 @@ class ShortcutsService { if (displays.length > 1) { displays.forEach((display, index) => { const key = `${modifier}+Shift+${index + 1}`; - globalShortcut.register(key, () => this.movementManager.moveToDisplay(display.id)); + globalShortcut.register(key, () => internalBridge.emit('window:moveToDisplay', { displayId: display.id })); }); } @@ -190,7 +188,7 @@ class ShortcutsService { ]; edgeDirections.forEach(({ key, direction }) => { globalShortcut.register(key, () => { - if (header && header.isVisible()) this.movementManager.moveToEdge(direction); + if (header && header.isVisible()) internalBridge.emit('window:moveToEdge', { direction }); }); }); @@ -232,16 +230,16 @@ class ShortcutsService { }; break; case 'moveUp': - callback = () => { if (header && header.isVisible()) this.movementManager.moveStep('up'); }; + callback = () => { if (header && header.isVisible()) internalBridge.emit('window:moveStep', { direction: 'up' }); }; break; case 'moveDown': - callback = () => { if (header && header.isVisible()) this.movementManager.moveStep('down'); }; + callback = () => { if (header && header.isVisible()) internalBridge.emit('window:moveStep', { direction: 'down' }); }; break; case 'moveLeft': - callback = () => { if (header && header.isVisible()) this.movementManager.moveStep('left'); }; + callback = () => { if (header && header.isVisible()) internalBridge.emit('window:moveStep', { direction: 'left' }); }; break; case 'moveRight': - callback = () => { if (header && header.isVisible()) this.movementManager.moveStep('right'); }; + callback = () => { if (header && header.isVisible()) internalBridge.emit('window:moveStep', { direction: 'right' }); }; break; case 'toggleClickThrough': callback = () => { diff --git a/src/ui/settings/ShortCutSettingsView.js b/src/ui/settings/ShortCutSettingsView.js index 691a059..808eab4 100644 --- a/src/ui/settings/ShortCutSettingsView.js +++ b/src/ui/settings/ShortCutSettingsView.js @@ -171,6 +171,7 @@ export class ShortcutSettingsView extends LitElement { async handleSave() { if (!window.api) return; + this.feedback = {}; const result = await window.api.shortcutSettingsView.saveShortcuts(this.shortcuts); if (!result.success) { alert('Failed to save shortcuts: ' + result.error); @@ -179,6 +180,7 @@ export class ShortcutSettingsView extends LitElement { handleClose() { if (!window.api) return; + this.feedback = {}; window.api.shortcutSettingsView.closeShortcutSettingsWindow(); } diff --git a/src/window/windowManager.js b/src/window/windowManager.js index 692199e..ac558db 100644 --- a/src/window/windowManager.js +++ b/src/window/windowManager.js @@ -30,8 +30,6 @@ if (shouldUseLiquidGlass) { let isContentProtectionOn = true; let lastVisibleWindows = new Set(['header']); -const HEADER_HEIGHT = 47; -const DEFAULT_WINDOW_WIDTH = 353; let currentHeaderState = 'apikey'; const windowPool = new Map(); @@ -47,22 +45,18 @@ function updateLayout() { } let movementManager = null; - -const FADE_DURATION = 250; -const FADE_FPS = 60; - /** - * 윈도우 투명도를 서서히 변경한다. * @param {BrowserWindow} win * @param {number} from * @param {number} to * @param {number} duration * @param {Function=} onComplete */ -function fadeWindow(win, from, to, duration = FADE_DURATION, onComplete) { +function fadeWindow(win, from, to, duration = 250, onComplete) { if (!win || win.isDestroyed()) return; - const steps = Math.max(1, Math.round(duration / (1000 / FADE_FPS))); + const FPS = 60; + const steps = Math.max(1, Math.round(duration / (1000 / FPS))); let currentStep = 0; win.setOpacity(from); @@ -83,7 +77,7 @@ function fadeWindow(win, from, to, duration = FADE_DURATION, onComplete) { win.setOpacity(to); onComplete && onComplete(); } - }, 1000 / FADE_FPS); + }, 1000 / FPS); } const showSettingsWindow = () => { @@ -106,6 +100,15 @@ function setupWindowController(windowPool, layoutManager, movementManager) { 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); + }); } function changeAllWindowsVisibility(windowPool, targetVisibility) { @@ -302,7 +305,7 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement } else { const currentBounds = win.getBounds(); fadeWindow( - win, 1, 0, FADE_DURATION, + win, 1, 0, undefined, () => win.hide() ); if (name === 'listen') { @@ -625,6 +628,9 @@ function getDisplayById(displayId) { function createWindows() { + const HEADER_HEIGHT = 47; + const DEFAULT_WINDOW_WIDTH = 353; + const primaryDisplay = screen.getPrimaryDisplay(); const { y: workAreaY, width: screenWidth } = primaryDisplay.workArea; @@ -685,7 +691,7 @@ function createWindows() { layoutManager = new WindowLayoutManager(windowPool); header.webContents.once('dom-ready', () => { - shortcutsService.initialize(movementManager, windowPool); + shortcutsService.initialize(windowPool); shortcutsService.registerShortcuts(); }); From f755fdb9e3e703d4b62ef768c6c2bde448e9d894 Mon Sep 17 00:00:00 2001 From: sanio Date: Tue, 15 Jul 2025 18:00:31 +0900 Subject: [PATCH 2/4] cleaning dependency in windowmanager --- src/bridge/featureBridge.js | 1 + src/bridge/windowBridge.js | 1 - src/features/listen/listenService.js | 48 +---- src/preload.js | 2 +- src/ui/listen/audioCore/listenCapture.js | 12 ++ src/window/smoothMovementManager.js | 86 +++------ src/window/windowLayoutManager.js | 104 +++++++++++ src/window/windowManager.js | 218 ++++++++--------------- 8 files changed, 215 insertions(+), 257 deletions(-) diff --git a/src/bridge/featureBridge.js b/src/bridge/featureBridge.js index be6d348..b7f1a6c 100644 --- a/src/bridge/featureBridge.js +++ b/src/bridge/featureBridge.js @@ -91,6 +91,7 @@ module.exports = { ipcMain.handle('listen:startMacosSystemAudio', async () => await listenService.handleStartMacosAudio()); ipcMain.handle('listen:stopMacosSystemAudio', async () => await listenService.handleStopMacosAudio()); ipcMain.handle('update-google-search-setting', async (event, enabled) => await listenService.handleUpdateGoogleSearchSetting(enabled)); + ipcMain.handle('listen:isSessionActive', async () => await listenService.isSessionActive()); ipcMain.handle('listen:changeSession', async (event, listenButtonText) => { console.log('[FeatureBridge] listen:changeSession from mainheader', listenButtonText); try { diff --git a/src/bridge/windowBridge.js b/src/bridge/windowBridge.js index 313273c..054ce0b 100644 --- a/src/bridge/windowBridge.js +++ b/src/bridge/windowBridge.js @@ -20,7 +20,6 @@ module.exports = { ipcMain.on('header-state-changed', (event, state) => windowManager.handleHeaderStateChanged(state)); ipcMain.on('header-animation-finished', (event, state) => windowManager.handleHeaderAnimationFinished(state)); ipcMain.handle('get-header-position', () => windowManager.getHeaderPosition()); - ipcMain.handle('move-header', (event, newX, newY) => windowManager.moveHeader(newX, newY)); ipcMain.handle('move-header-to', (event, newX, newY) => windowManager.moveHeaderTo(newX, newY)); ipcMain.handle('adjust-window-height', (event, targetHeight) => windowManager.adjustWindowHeight(event.sender, targetHeight)); }, diff --git a/src/features/listen/listenService.js b/src/features/listen/listenService.js index d879d60..e3a32c7 100644 --- a/src/features/listen/listenService.js +++ b/src/features/listen/listenService.js @@ -54,53 +54,7 @@ class ListenService { } async handleListenRequest(listenButtonText) { - const { windowPool, updateLayout } = require('../../window/windowManager'); - const listenWindow = windowPool.get('listen'); - const header = windowPool.get('header'); - - try { - switch (listenButtonText) { - case 'Listen': - console.log('[ListenService] changeSession to "Listen"'); - listenWindow.show(); - updateLayout(); - listenWindow.webContents.send('window-show-animation'); - await this.initializeSession(); - listenWindow.webContents.send('session-state-changed', { isActive: true }); - break; - - case 'Stop': - console.log('[ListenService] changeSession to "Stop"'); - await this.closeSession(); - listenWindow.webContents.send('session-state-changed', { isActive: false }); - break; - - case 'Done': - console.log('[ListenService] changeSession to "Done"'); - listenWindow.webContents.send('window-hide-animation'); - listenWindow.webContents.send('session-state-changed', { isActive: false }); - break; - - default: - throw new Error(`[ListenService] unknown listenButtonText: ${listenButtonText}`); - } - - header.webContents.send('listen:changeSessionResult', { success: true }); - - } catch (error) { - console.error('[ListenService] error in handleListenRequest:', error); - header.webContents.send('listen:changeSessionResult', { success: false }); - throw error; - } - } - - initialize() { - this.setupIpcHandlers(); - console.log('[ListenService] Initialized and ready.'); - } - - async handleListenRequest(listenButtonText) { - const { windowPool, updateLayout } = require('../../window/windowManager'); + const { windowPool } = require('../../window/windowManager'); const listenWindow = windowPool.get('listen'); const header = windowPool.get('header'); diff --git a/src/preload.js b/src/preload.js index 04b1046..2f4112c 100644 --- a/src/preload.js +++ b/src/preload.js @@ -273,7 +273,7 @@ contextBridge.exposeInMainWorld('api', { stopMacosSystemAudio: () => ipcRenderer.invoke('listen:stopMacosSystemAudio'), // Session Management - isSessionActive: () => ipcRenderer.invoke('is-session-active'), + isSessionActive: () => ipcRenderer.invoke('listen:isSessionActive'), // Listeners onSystemAudioData: (callback) => ipcRenderer.on('system-audio-data', callback), diff --git a/src/ui/listen/audioCore/listenCapture.js b/src/ui/listen/audioCore/listenCapture.js index 2f52f25..ade889e 100644 --- a/src/ui/listen/audioCore/listenCapture.js +++ b/src/ui/listen/audioCore/listenCapture.js @@ -422,6 +422,12 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu try { if (isMacOS) { + + const sessionActive = await window.api.listenCapture.isSessionActive(); + if (!sessionActive) { + throw new Error('STT sessions not initialized - please wait for initialization to complete'); + } + // On macOS, use SystemAudioDump for audio and getDisplayMedia for screen console.log('Starting macOS capture with SystemAudioDump...'); @@ -466,6 +472,12 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu console.log('macOS screen capture started - audio handled by SystemAudioDump'); } else if (isLinux) { + + const sessionActive = await window.api.listenCapture.isSessionActive(); + if (!sessionActive) { + throw new Error('STT sessions not initialized - please wait for initialization to complete'); + } + // Linux - use display media for screen capture and getUserMedia for microphone mediaStream = await navigator.mediaDevices.getDisplayMedia({ video: { diff --git a/src/window/smoothMovementManager.js b/src/window/smoothMovementManager.js index c81f0a4..044fb00 100644 --- a/src/window/smoothMovementManager.js +++ b/src/window/smoothMovementManager.js @@ -1,11 +1,27 @@ 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, getDisplayById, getCurrentDisplay, updateLayout) { + constructor(windowPool, layoutManager) { this.windowPool = windowPool; - this.getDisplayById = getDisplayById; - this.getCurrentDisplay = getCurrentDisplay; - this.updateLayout = updateLayout; + this.layoutManager = layoutManager; this.stepSize = 80; this.animationDuration = 300; this.headerPosition = { x: 0, y: 0 }; @@ -39,11 +55,11 @@ class SmoothMovementManager { const header = this.windowPool.get('header'); if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return; - const targetDisplay = this.getDisplayById(displayId); + const targetDisplay = getDisplayById(displayId); if (!targetDisplay) return; const currentBounds = header.getBounds(); - const currentDisplay = this.getCurrentDisplay(header); + const currentDisplay = getCurrentDisplay(header); if (currentDisplay.id === targetDisplay.id) return; @@ -60,56 +76,6 @@ class SmoothMovementManager { this.currentDisplayId = targetDisplay.id; } - hideToEdge(edge, callback, { instant = false } = {}) { - const header = this.windowPool.get('header'); - if (!header || header.isDestroyed()) { - if (typeof callback === 'function') callback(); - return; - } - - const { x, y } = header.getBounds(); - this.lastVisiblePosition = { x, y }; - this.hiddenPosition = { edge }; - - if (instant) { - header.hide(); - if (typeof callback === 'function') callback(); - return; - } - - header.webContents.send('window-hide-animation'); - - setTimeout(() => { - if (!header.isDestroyed()) header.hide(); - if (typeof callback === 'function') callback(); - }, 5); - } - - showFromEdge(callback) { - const header = this.windowPool.get('header'); - if (!header || header.isDestroyed()) { - if (typeof callback === 'function') callback(); - return; - } - - // 숨기기 전에 기억해둔 위치 복구 - if (this.lastVisiblePosition) { - header.setPosition( - this.lastVisiblePosition.x, - this.lastVisiblePosition.y, - false // animate: false - ); - } - - header.show(); - header.webContents.send('window-show-animation'); - - // 내부 상태 초기화 - this.hiddenPosition = null; - this.lastVisiblePosition = null; - - if (typeof callback === 'function') callback(); - } moveStep(direction) { const header = this.windowPool.get('header'); @@ -211,7 +177,7 @@ class SmoothMovementManager { setTimeout(step, 8); // requestAnimationFrame 대신 setTimeout으로 간결하게 처리 } else { // 애니메이션 종료 - this.updateLayout(); // 레이아웃 재정렬 + this.layoutManager.updateLayout(); // 레이아웃 재정렬 if (onComplete) { onComplete(); // 완료 콜백 실행 } @@ -267,7 +233,7 @@ class SmoothMovementManager { // Update header position to the actual final position this.headerPosition = { x: Math.round(targetX), y: Math.round(targetY) }; } - this.updateLayout(); + this.layoutManager.updateLayout(); } }; animate(); @@ -277,7 +243,7 @@ class SmoothMovementManager { const header = this.windowPool.get('header'); if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return; - const display = this.getCurrentDisplay(header); + const display = getCurrentDisplay(header); const { width, height } = display.workAreaSize; const { x: workAreaX, y: workAreaY } = display.workArea; const currentBounds = header.getBounds(); @@ -313,7 +279,7 @@ class SmoothMovementManager { }); this.headerPosition = { x: targetX, y: targetY }; - this.updateLayout(); + this.layoutManager.updateLayout(); } destroy() { diff --git a/src/window/windowLayoutManager.js b/src/window/windowLayoutManager.js index cdd0ef3..f4ba051 100644 --- a/src/window/windowLayoutManager.js +++ b/src/window/windowLayoutManager.js @@ -37,6 +37,110 @@ class WindowLayoutManager { }); } + getHeaderPosition = () => { + const header = this.windowPool.get('header'); + if (header) { + const [x, y] = header.getPosition(); + return { x, y }; + } + 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 } diff --git a/src/window/windowManager.js b/src/window/windowManager.js index ac558db..f57dcd5 100644 --- a/src/window/windowManager.js +++ b/src/window/windowManager.js @@ -38,11 +38,6 @@ let settingsHideTimer = null; let layoutManager = null; -function updateLayout() { - if (layoutManager) { - layoutManager.updateLayout(); - } -} let movementManager = null; /** @@ -92,6 +87,30 @@ const cancelHideSettingsWindow = () => { internalBridge.emit('window:requestVisibility', { name: 'settings', visible: true }); }; +const moveWindowStep = (direction) => { + internalBridge.emit('window:moveStep', { direction }); +}; + +const resizeHeaderWindow = ({ width, height }) => { + internalBridge.emit('window:resizeHeaderWindow', { width, height }); +}; + +const handleHeaderAnimationFinished = (state) => { + internalBridge.emit('window:headerAnimationFinished', state); +}; + +const getHeaderPosition = () => { + internalBridge.emit('window:getHeaderPosition'); +}; + +const moveHeaderTo = (newX, newY) => { + internalBridge.emit('window:moveHeaderTo', { newX, newY }); +}; + +const adjustWindowHeight = (sender, targetHeight) => { + internalBridge.emit('window:adjustWindowHeight', { sender, targetHeight }); +}; + function setupWindowController(windowPool, layoutManager, movementManager) { internalBridge.on('window:requestVisibility', ({ name, visible }) => { @@ -109,6 +128,21 @@ function setupWindowController(windowPool, layoutManager, movementManager) { 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 changeAllWindowsVisibility(windowPool, targetVisibility) { @@ -249,8 +283,8 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement const otherWin = windowPool.get(otherName); const isOtherWinVisible = otherWin && !otherWin.isDestroyed() && otherWin.isVisible(); - const ANIM_OFFSET_X = 100; - const ANIM_OFFSET_Y = 20; + const ANIM_OFFSET_X = 50; + const ANIM_OFFSET_Y = 20; if (shouldBeVisible) { win.setOpacity(0); @@ -305,7 +339,7 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement } else { const currentBounds = win.getBounds(); fadeWindow( - win, 1, 0, undefined, + win, 1, 0, 250, () => win.hide() ); if (name === 'listen') { @@ -313,7 +347,7 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement const targetX = currentBounds.x - ANIM_OFFSET_X; movementManager.animateWindow(win, targetX, currentBounds.y); } else { - const targetX = currentBounds.x - currentBounds.width; + const targetX = currentBounds.x - ANIM_OFFSET_X; movementManager.animateWindow(win, targetX, currentBounds.y); } } else if (name === 'ask') { @@ -353,52 +387,6 @@ const toggleContentProtection = () => { return newStatus; }; -const resizeHeaderWindow = ({ width, height }) => { - const header = windowPool.get('header'); - if (header) { - console.log(`[WindowManager] Resize request: ${width}x${height}`); - - if (movementManager && movementManager.isAnimating) { - console.log('[WindowManager] Skipping resize during animation'); - return { success: false, error: 'Cannot resize during animation' }; - } - - 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); - } - - if (updateLayout) { - updateLayout(); - } - - return { success: true }; - } - return { success: false, error: 'Header window not found' }; -}; - const openLoginPage = () => { const webUrl = process.env.pickleglass_WEB_URL || 'http://localhost:3000'; @@ -407,12 +395,6 @@ const openLoginPage = () => { console.log('Opening personalization page:', personalizeUrl); }; -const moveWindowStep = (direction) => { - if (movementManager) { - movementManager.moveStep(direction); - } -}; - function createFeatureWindows(header, namesToCreate) { // if (windowPool.has('listen')) return; @@ -617,14 +599,6 @@ function getCurrentDisplay(window) { return screen.getDisplayNearestPoint(windowCenter); } -function getDisplayById(displayId) { - const displays = screen.getAllDisplays(); - return displays.find(d => d.id === displayId) || screen.getPrimaryDisplay(); -} - - - - function createWindows() { @@ -636,8 +610,7 @@ function createWindows() { const initialX = Math.round((screenWidth - DEFAULT_WINDOW_WIDTH) / 2); const initialY = workAreaY + 21; - movementManager = new SmoothMovementManager(windowPool, getDisplayById, getCurrentDisplay, updateLayout); - + const header = new BrowserWindow({ width: DEFAULT_WINDOW_WIDTH, height: HEADER_HEIGHT, @@ -687,15 +660,17 @@ function createWindows() { }); } windowPool.set('header', header); - header.on('moved', updateLayout); layoutManager = new WindowLayoutManager(windowPool); + movementManager = new SmoothMovementManager(windowPool, layoutManager); + + header.on('moved', () => layoutManager.updateLayout()); header.webContents.once('dom-ready', () => { shortcutsService.initialize(windowPool); shortcutsService.registerShortcuts(); }); - setupIpcHandlers(movementManager); + setupIpcHandlers(movementManager, layoutManager); setupWindowController(windowPool, layoutManager, movementManager); if (currentHeaderState === 'main') { @@ -729,13 +704,13 @@ function createWindows() { header.on('resize', () => { console.log('[WindowManager] Header resize event triggered'); - updateLayout(); + layoutManager.updateLayout(); }); return windowPool; } -function setupIpcHandlers(movementManager) { +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); @@ -752,10 +727,11 @@ function setupIpcHandlers(movementManager) { screen.on('display-metrics-changed', (event, display, changedMetrics) => { // console.log('[Display] Display metrics changed:', display.id, changedMetrics); - updateLayout(); + layoutManager.updateLayout(); }); } + const handleHeaderStateChanged = (state) => { console.log(`[WindowManager] Header state changed to: ${state}`); currentHeaderState = state; @@ -768,7 +744,17 @@ const handleHeaderStateChanged = (state) => { internalBridge.emit('reregister-shortcuts'); }; -const handleHeaderAnimationFinished = (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; @@ -777,87 +763,24 @@ const handleHeaderAnimationFinished = (state) => { console.log('[WindowManager] Header hidden after animation.'); } else if (state === 'visible') { console.log('[WindowManager] Header shown after animation.'); - updateLayout(); + layoutManager.updateLayout(); } }; -const getHeaderPosition = () => { - const header = windowPool.get('header'); - if (header) { - const [x, y] = header.getPosition(); - return { x, y }; - } - return { x: 0, y: 0 }; +const gettingHeaderPosition = (layoutManager) => { + return layoutManager.getHeaderPosition(); }; -const moveHeader = (newX, newY) => { - const header = windowPool.get('header'); - if (header) { - const currentY = newY !== undefined ? newY : header.getBounds().y; - header.setPosition(newX, currentY, false); - updateLayout(); - } +const movingHeaderTo = (layoutManager, newX, newY) => { + layoutManager.moveHeaderTo(newX, newY); }; -const moveHeaderTo = (newX, newY) => { - const header = 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); - updateLayout(); - } -}; - -const adjustWindowHeight = (sender, targetHeight) => { - const senderWindow = BrowserWindow.fromWebContents(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); - } - - updateLayout(); - } +const adjustingWindowHeight = (layoutManager, sender, targetHeight) => { + layoutManager.adjustWindowHeight(sender, targetHeight); }; module.exports = { - updateLayout, createWindows, windowPool, toggleContentProtection, @@ -871,7 +794,6 @@ module.exports = { handleHeaderStateChanged, handleHeaderAnimationFinished, getHeaderPosition, - moveHeader, moveHeaderTo, adjustWindowHeight, }; \ No newline at end of file From ecae4050bbbf4ec264400de8f5eb00812275ae59 Mon Sep 17 00:00:00 2001 From: sanio Date: Tue, 15 Jul 2025 18:21:22 +0900 Subject: [PATCH 3/4] refactored layoutmanager, movementmanager --- src/window/smoothMovementManager.js | 385 +++++++++------- src/window/windowLayoutManager.js | 658 ++++++++++++++++++---------- src/window/windowManager.js | 503 ++++++++++++++------- 3 files changed, 1009 insertions(+), 537 deletions(-) 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 = { From bba38ac56f13612f539850a9e206b72c77f8d491 Mon Sep 17 00:00:00 2001 From: sanio Date: Tue, 15 Jul 2025 20:10:46 +0900 Subject: [PATCH 4/4] refactor windowmanager finished --- src/window/smoothMovementManager.js | 256 +++------------------- src/window/windowLayoutManager.js | 327 ++-------------------------- src/window/windowManager.js | 272 ++++------------------- 3 files changed, 94 insertions(+), 761 deletions(-) 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,