From f8eb91c4ade802e31ab2ef15efc9f90830e25adc Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Thu, 3 Jul 2025 03:40:43 +0200 Subject: [PATCH] Make batch preview menu draggable and position-aware Added draggable functionality to the batch preview menu, allowing users to reposition it within the canvas using world coordinates. The menu's position now updates with viewport changes, and its initial placement is centered below the output area. Also refactored logic to show the menu with new layers instead of adding to an existing batch. --- js/BatchPreviewManager.js | 81 ++++++++++++++++++++++++++++++--------- js/Canvas.js | 4 +- js/CanvasRenderer.js | 5 +++ 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/js/BatchPreviewManager.js b/js/BatchPreviewManager.js index 8989fef..4a54999 100644 --- a/js/BatchPreviewManager.js +++ b/js/BatchPreviewManager.js @@ -11,6 +11,25 @@ export class BatchPreviewManager { this.element = null; this.uiInitialized = false; this.maskWasVisible = false; + + // Position in canvas world coordinates + this.worldX = 0; + this.worldY = 0; + this.isDragging = false; + } + + updateScreenPosition(viewport) { + if (!this.active || !this.element) return; + + // Translate world coordinates to screen coordinates + const screenX = (this.worldX - viewport.x) * viewport.zoom; + const screenY = (this.worldY - viewport.y) * viewport.zoom; + + // We can also scale the menu with zoom, but let's keep it constant for now for readability + const scale = 1; // viewport.zoom; + + // Use transform for performance + this.element.style.transform = `translate(${screenX}px, ${screenY}px) scale(${scale})`; } _createUI() { @@ -20,9 +39,8 @@ export class BatchPreviewManager { this.element.id = 'layerforge-batch-preview'; this.element.style.cssText = ` position: absolute; - bottom: 20px; - left: 50%; - transform: translateX(-50%); + top: 0; + left: 0; background-color: #333; color: white; padding: 8px 15px; @@ -34,8 +52,42 @@ export class BatchPreviewManager { font-family: sans-serif; z-index: 1001; border: 1px solid #555; + cursor: move; + user-select: none; `; + this.element.addEventListener('mousedown', (e) => { + if (e.target.tagName === 'BUTTON') return; + + e.preventDefault(); + e.stopPropagation(); + + this.isDragging = true; + + const handleMouseMove = (moveEvent) => { + if (this.isDragging) { + // Convert screen pixel movement to world coordinate movement + const deltaX = moveEvent.movementX / this.canvas.viewport.zoom; + const deltaY = moveEvent.movementY / this.canvas.viewport.zoom; + + this.worldX += deltaX; + this.worldY += deltaY; + + // The render loop will handle updating the screen position, but we need to trigger it. + this.canvas.render(); + } + }; + + const handleMouseUp = () => { + this.isDragging = false; + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }); + const prevButton = this._createButton('◀', 'Previous'); // Left arrow const nextButton = this._createButton('▶', 'Next'); // Right arrow const confirmButton = this._createButton('✔', 'Confirm'); // Checkmark @@ -91,6 +143,13 @@ export class BatchPreviewManager { this._createUI(); + // Set initial position to be centered horizontally and just below the output area + const menuWidthInWorld = this.element.offsetWidth / this.canvas.viewport.zoom; + const paddingInWorld = 20 / this.canvas.viewport.zoom; // 20px padding in screen space + + this.worldX = (this.canvas.width / 2) - (menuWidthInWorld / 2); + this.worldY = this.canvas.height + paddingInWorld; + // Auto-hide mask logic this.maskWasVisible = this.canvas.maskTool.isOverlayVisible; if (this.maskWasVisible) { @@ -134,22 +193,6 @@ export class BatchPreviewManager { this.canvas.render(); } - addLayers(newLayers) { - if (!newLayers || newLayers.length === 0) { - return; - } - - if (this.active) { - // UI is already open, just add the new layers - log.info(`Adding ${newLayers.length} new layers to active batch preview.`); - this.layers.push(...newLayers); - this._update(); - } else { - // UI is not open, show it with the new layers - this.show(newLayers); - } - } - navigate(direction) { this.currentIndex += direction; if (this.currentIndex < 0) { diff --git a/js/Canvas.js b/js/Canvas.js index d95e6b9..20bd719 100644 --- a/js/Canvas.js +++ b/js/Canvas.js @@ -493,8 +493,8 @@ export class Canvas { log.info('Auto-refresh triggered, importing latest images.'); const newLayers = await this.canvasIO.importLatestImages(lastExecutionStartTime); - if (newLayers && newLayers.length > 0) { - this.batchPreviewManager.addLayers(newLayers); + if (newLayers && newLayers.length > 1) { + this.batchPreviewManager.show(newLayers); } } }; diff --git a/js/CanvasRenderer.js b/js/CanvasRenderer.js index e128158..237a25b 100644 --- a/js/CanvasRenderer.js +++ b/js/CanvasRenderer.js @@ -112,6 +112,11 @@ export class CanvasRenderer { this.canvas.canvas.height = this.canvas.offscreenCanvas.height; } this.canvas.ctx.drawImage(this.canvas.offscreenCanvas, 0, 0); + + // Update Batch Preview UI position + if (this.canvas.batchPreviewManager) { + this.canvas.batchPreviewManager.updateScreenPosition(this.canvas.viewport); + } } renderInteractionElements(ctx) {