From b2ff5666f92afaf815baacd337687897f9967890 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Tue, 1 Jul 2025 16:37:18 +0200 Subject: [PATCH] Add draggable blend mode menu and improve right-click UX Introduces a draggable title bar to the blend mode menu for better usability and restricts its movement within the viewport. Right-clicking on selected layers now opens the blend mode menu, and the default context menu is suppressed on the canvas. Also refines tooltip table cell sizing for improved display. --- js/CanvasInteractions.js | 36 +++++++++++++++----- js/CanvasLayers.js | 73 +++++++++++++++++++++++++++++++++++++--- js/CanvasView.js | 5 +-- 3 files changed, 98 insertions(+), 16 deletions(-) diff --git a/js/CanvasInteractions.js b/js/CanvasInteractions.js index bd9b78f..c710aae 100644 --- a/js/CanvasInteractions.js +++ b/js/CanvasInteractions.js @@ -51,6 +51,9 @@ export class CanvasInteractions { this.canvas.canvas.addEventListener('dragenter', this.handleDragEnter.bind(this)); this.canvas.canvas.addEventListener('dragleave', this.handleDragLeave.bind(this)); this.canvas.canvas.addEventListener('drop', this.handleDrop.bind(this)); + + // Prevent default context menu on canvas + this.canvas.canvas.addEventListener('contextmenu', this.handleContextMenu.bind(this)); } resetInteractionState() { @@ -95,6 +98,23 @@ export class CanvasInteractions { } this.interaction.lastClickTime = currentTime; + // Check for right click to show blend mode menu on selected layers + if (e.button === 2) { + const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(worldCoords.x, worldCoords.y); + if (clickedLayerResult && this.canvas.selectedLayers.includes(clickedLayerResult.layer)) { + e.preventDefault(); // Prevent context menu + this.canvas.canvasLayers.showBlendModeMenu(viewCoords.x ,viewCoords.y); + return; + } + } + + // Check for Shift key first to start output area drawing, ignoring layers + if (e.shiftKey) { + this.startCanvasResize(worldCoords); + this.canvas.render(); + return; + } + const transformTarget = this.canvas.canvasLayers.getHandleAtPosition(worldCoords.x, worldCoords.y); if (transformTarget) { this.startLayerTransform(transformTarget.layer, transformTarget.handle, worldCoords); @@ -103,18 +123,11 @@ export class CanvasInteractions { const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(worldCoords.x, worldCoords.y); if (clickedLayerResult) { - if (e.shiftKey && this.canvas.selectedLayers.includes(clickedLayerResult.layer)) { - this.canvas.canvasLayers.showBlendModeMenu(e.clientX, e.clientY); - return; - } this.startLayerDrag(clickedLayerResult.layer, worldCoords); return; } - if (e.shiftKey) { - this.startCanvasResize(worldCoords); - } else { - this.startPanning(e); - } + + this.startPanning(e); this.canvas.render(); } @@ -216,6 +229,11 @@ export class CanvasInteractions { } } + handleContextMenu(e) { + // Prevent default context menu on canvas + e.preventDefault(); + } + handleWheel(e) { e.preventDefault(); if (this.canvas.maskTool.isActive) { diff --git a/js/CanvasLayers.js b/js/CanvasLayers.js index 7125691..4a3fbfa 100644 --- a/js/CanvasLayers.js +++ b/js/CanvasLayers.js @@ -473,11 +473,74 @@ export class CanvasLayers { background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 4px; - padding: 5px; z-index: 10000; box-shadow: 0 2px 10px rgba(0,0,0,0.3); + min-width: 200px; `; + // Create draggable title bar + const titleBar = document.createElement('div'); + titleBar.style.cssText = ` + background: #3a3a3a; + color: white; + padding: 8px 10px; + cursor: move; + user-select: none; + border-radius: 3px 3px 0 0; + font-size: 12px; + font-weight: bold; + border-bottom: 1px solid #4a4a4a; + `; + titleBar.textContent = 'Blend Mode'; + + // Create content area + const content = document.createElement('div'); + content.style.cssText = ` + padding: 5px; + `; + + menu.appendChild(titleBar); + menu.appendChild(content); + + // Add drag functionality + let isDragging = false; + let dragOffset = { x: 0, y: 0 }; + + const handleMouseMove = (e) => { + if (isDragging) { + const newX = e.clientX - dragOffset.x; + const newY = e.clientY - dragOffset.y; + + // Keep menu within viewport bounds + const maxX = window.innerWidth - menu.offsetWidth; + const maxY = window.innerHeight - menu.offsetHeight; + + menu.style.left = Math.max(0, Math.min(newX, maxX)) + 'px'; + menu.style.top = Math.max(0, Math.min(newY, maxY)) + 'px'; + } + }; + + const handleMouseUp = () => { + if (isDragging) { + isDragging = false; + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + } + }; + + titleBar.addEventListener('mousedown', (e) => { + isDragging = true; + // Calculate offset from mouse position to menu's top-left corner + dragOffset.x = e.clientX - parseInt(menu.style.left); + dragOffset.y = e.clientY - parseInt(menu.style.top); + e.preventDefault(); + e.stopPropagation(); + + // Add global event listeners for dragging + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }); + this.blendModes.forEach(mode => { const container = document.createElement('div'); container.className = 'blend-mode-container'; @@ -512,10 +575,10 @@ export class CanvasLayers { } option.onclick = () => { - menu.querySelectorAll('input[type="range"]').forEach(s => { + content.querySelectorAll('input[type="range"]').forEach(s => { s.style.display = 'none'; }); - menu.querySelectorAll('.blend-mode-container div').forEach(d => { + content.querySelectorAll('.blend-mode-container div').forEach(d => { d.style.backgroundColor = ''; }); @@ -558,14 +621,14 @@ export class CanvasLayers { container.appendChild(option); container.appendChild(slider); - menu.appendChild(container); + content.appendChild(container); }); const container = this.canvas.canvas.parentElement || document.body; container.appendChild(menu); const closeMenu = (e) => { - if (!menu.contains(e.target)) { + if (!menu.contains(e.target) && !isDragging) { this.closeBlendModeMenu(); document.removeEventListener('mousedown', closeMenu); } diff --git a/js/CanvasView.js b/js/CanvasView.js index cda1aa1..4095ed5 100644 --- a/js/CanvasView.js +++ b/js/CanvasView.js @@ -241,12 +241,13 @@ async function createCanvasWidget(node, widget, app) { } .painter-tooltip table td:first-child { - width: 45%; + width: auto; white-space: nowrap; + min-width: fit-content; } .painter-tooltip table td:last-child { - width: 55%; + width: auto; } .painter-tooltip table tr:nth-child(odd) td {