diff --git a/src/electron/smoothMovementManager.js b/src/electron/smoothMovementManager.js index a676c82..ba9eec0 100644 --- a/src/electron/smoothMovementManager.js +++ b/src/electron/smoothMovementManager.js @@ -120,6 +120,13 @@ class SmoothMovementManager { 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; @@ -128,24 +135,42 @@ class SmoothMovementManager { default: return; } - const displays = screen.getAllDisplays(); - let validPosition = displays.some(d => ( - targetX >= d.workArea.x && targetX + currentBounds.width <= d.workArea.x + d.workArea.width && - targetY >= d.workArea.y && targetY + currentBounds.height <= d.workArea.y + d.workArea.height - )); - - if (!validPosition) { - const nearestDisplay = screen.getDisplayNearestPoint({ x: targetX, y: targetY }); - const { x, y, width, height } = nearestDisplay.workArea; - targetX = Math.max(x, Math.min(x + width - currentBounds.width, targetX)); - targetY = Math.max(y, Math.min(y + height - currentBounds.height, targetY)); + // 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}`); } - if (targetX === this.headerPosition.x && targetY === this.headerPosition.y) return; - this.animateToPosition(header, targetX, targetY); + 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); } - animateToPosition(header, targetX, targetY) { + animateToPosition(header, targetX, targetY, windowSize) { if (!this._isWindowValid(header)) return; this.isAnimating = true; @@ -173,18 +198,25 @@ class SmoothMovementManager { } if (!this._isWindowValid(header)) return; - header.setPosition(Math.round(currentX), Math.round(currentY)); + 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.headerPosition = { x: targetX, y: targetY }; + 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.isAnimating = false; this.updateLayout(); } }; @@ -198,20 +230,40 @@ class SmoothMovementManager { const display = this.getCurrentDisplay(header); const { width, height } = display.workAreaSize; const { x: workAreaX, y: workAreaY } = display.workArea; - const headerBounds = header.getBounds(); 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 - headerBounds.width; break; - case 'up': targetY = workAreaY; break; - case 'down': targetY = workAreaY + height - headerBounds.height; break; + 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; } - this.headerPosition = { x: currentBounds.x, y: currentBounds.y }; - this.animateToPosition(header, targetX, targetY); + header.setBounds({ + x: Math.round(targetX), + y: Math.round(targetY), + width: windowSize.width, + height: windowSize.height + }); + + this.headerPosition = { x: targetX, y: targetY }; + this.updateLayout(); } destroy() { @@ -224,4 +276,4 @@ class SmoothMovementManager { } } -module.exports = SmoothMovementManager; \ No newline at end of file +module.exports = SmoothMovementManager; diff --git a/src/electron/windowManager.js b/src/electron/windowManager.js index ff85e16..470ca94 100644 --- a/src/electron/windowManager.js +++ b/src/electron/windowManager.js @@ -428,7 +428,13 @@ function createWindows() { contextIsolation: false, backgroundThrottling: false, webSecurity: false, + enableRemoteModule: false, + // Ensure proper rendering and prevent pixelation + experimentalFeatures: false, }, + // Prevent pixelation and ensure proper rendering + useContentSize: true, + disableAutoHideCursor: true, }); if (process.platform === 'darwin') { header.setWindowButtonVisibility(false); @@ -492,7 +498,10 @@ function createWindows() { } }); - header.on('resize', updateLayout); + header.on('resize', () => { + console.log('[WindowManager] Header resize event triggered'); + updateLayout(); + }); ipcMain.handle('toggle-all-windows-visibility', () => toggleAllWindowsVisibility(movementManager)); @@ -957,19 +966,49 @@ function setupIpcHandlers(movementManager) { ipcMain.handle('resize-header-window', (event, { width, height }) => { const header = windowPool.get('header'); if (header) { + console.log(`[WindowManager] Resize request: ${width}x${height}`); + + // Prevent resizing during animations or if already at target size + 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})`); + + // Skip if already at target size to prevent unnecessary operations + 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 bounds = header.getBounds(); - const newX = bounds.x + Math.round((bounds.width - width) / 2); + // Calculate the center point of the current window + const centerX = currentBounds.x + currentBounds.width / 2; + // Calculate new X position to keep the window centered + const newX = Math.round(centerX - width / 2); - header.setBounds({ x: newX, y: bounds.y, width, height }); + // Get the current display to ensure we stay within bounds + const display = getCurrentDisplay(header); + const { x: workAreaX, width: workAreaWidth } = display.workArea; + + // Clamp the new position to stay within display bounds + 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); } + + // Update layout after resize + updateLayout(); + return { success: true }; } return { success: false, error: 'Header window not found' }; @@ -1019,8 +1058,24 @@ function setupIpcHandlers(movementManager) { const { x: workAreaX, y: workAreaY, width, height } = targetDisplay.workArea; const headerBounds = header.getBounds(); - const clampedX = Math.max(workAreaX, Math.min(workAreaX + width - headerBounds.width, newX)); - const clampedY = Math.max(workAreaY, Math.min(workAreaY + height - headerBounds.height, newY)); + // Only clamp if the new position would actually go out of bounds + // This prevents progressive restriction of movement + let clampedX = newX; + let clampedY = newY; + + // Check if we need to clamp X position + if (newX < workAreaX) { + clampedX = workAreaX; + } else if (newX + headerBounds.width > workAreaX + width) { + clampedX = workAreaX + width - headerBounds.width; + } + + // Check if we need to clamp Y position + if (newY < workAreaY) { + clampedY = workAreaY; + } else if (newY + headerBounds.height > workAreaY + height) { + clampedY = workAreaY + height - headerBounds.height; + } header.setPosition(clampedX, clampedY, false); diff --git a/src/features/ask/AskView.js b/src/features/ask/AskView.js index ee0c153..ed4794f 100644 --- a/src/features/ask/AskView.js +++ b/src/features/ask/AskView.js @@ -98,6 +98,12 @@ export class AskView extends LitElement { user-select: none; } + /* Allow text selection in assistant responses */ + .response-container, .response-container * { + user-select: text !important; + cursor: text !important; + } + .response-container pre { background: rgba(0, 0, 0, 0.4) !important; border-radius: 8px !important; diff --git a/src/features/listen/AssistantView.js b/src/features/listen/AssistantView.js index 311a11f..9c6a7e9 100644 --- a/src/features/listen/AssistantView.js +++ b/src/features/listen/AssistantView.js @@ -84,6 +84,87 @@ export class AssistantView extends LitElement { user-select: none; } +/* Allow text selection in insights responses */ +.insights-container, .insights-container *, .markdown-content { + user-select: text !important; + cursor: text !important; +} + +/* highlight.js 스타일 추가 */ +.insights-container pre { + background: rgba(0, 0, 0, 0.4) !important; + border-radius: 8px !important; + padding: 12px !important; + margin: 8px 0 !important; + overflow-x: auto !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + white-space: pre !important; + word-wrap: normal !important; + word-break: normal !important; +} + +.insights-container code { + font-family: 'Monaco', 'Menlo', 'Consolas', monospace !important; + font-size: 11px !important; + background: transparent !important; + white-space: pre !important; + word-wrap: normal !important; + word-break: normal !important; +} + +.insights-container pre code { + white-space: pre !important; + word-wrap: normal !important; + word-break: normal !important; + display: block !important; +} + +.insights-container p code { + background: rgba(255, 255, 255, 0.1) !important; + padding: 2px 4px !important; + border-radius: 3px !important; + color: #ffd700 !important; +} + +.hljs-keyword { + color: #ff79c6 !important; +} + +.hljs-string { + color: #f1fa8c !important; +} + +.hljs-comment { + color: #6272a4 !important; +} + +.hljs-number { + color: #bd93f9 !important; +} + +.hljs-function { + color: #50fa7b !important; +} + +.hljs-title { + color: #50fa7b !important; +} + +.hljs-variable { + color: #8be9fd !important; +} + +.hljs-built_in { + color: #ffb86c !important; +} + +.hljs-attr { + color: #50fa7b !important; +} + +.hljs-tag { + color: #ff79c6 !important; +} .assistant-container { display: flex; flex-direction: column;