mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-25 14:25:44 -03:00
Add brush preview overlay to MaskTool
Introduces a brush preview overlay using a separate preview canvas in MaskTool. Mouse event handlers in CanvasInteractions and MaskTool are updated to support passing both world and view coordinates, enabling accurate brush preview rendering. The preview is shown or hidden appropriately on mouse enter/leave and while drawing.
This commit is contained in:
14
js/Canvas.js
14
js/Canvas.js
@@ -247,6 +247,20 @@ export class Canvas {
|
|||||||
return {x: worldX, y: worldY};
|
return {x: worldX, y: worldY};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMouseViewCoordinates(e) {
|
||||||
|
const rect = this.canvas.getBoundingClientRect();
|
||||||
|
const mouseX_DOM = e.clientX - rect.left;
|
||||||
|
const mouseY_DOM = e.clientY - rect.top;
|
||||||
|
|
||||||
|
const scaleX = this.canvas.width / rect.width;
|
||||||
|
const scaleY = this.canvas.height / rect.height;
|
||||||
|
|
||||||
|
const mouseX_Canvas = mouseX_DOM * scaleX;
|
||||||
|
const mouseY_Canvas = mouseY_DOM * scaleY;
|
||||||
|
|
||||||
|
return { x: mouseX_Canvas, y: mouseY_Canvas };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
moveLayer(fromIndex, toIndex) {
|
moveLayer(fromIndex, toIndex) {
|
||||||
return this.canvasLayers.moveLayer(fromIndex, toIndex);
|
return this.canvasLayers.moveLayer(fromIndex, toIndex);
|
||||||
|
|||||||
@@ -34,11 +34,13 @@ export class CanvasInteractions {
|
|||||||
this.canvas.canvas.addEventListener('keydown', this.handleKeyDown.bind(this));
|
this.canvas.canvas.addEventListener('keydown', this.handleKeyDown.bind(this));
|
||||||
this.canvas.canvas.addEventListener('keyup', this.handleKeyUp.bind(this));
|
this.canvas.canvas.addEventListener('keyup', this.handleKeyUp.bind(this));
|
||||||
|
|
||||||
this.canvas.canvas.addEventListener('mouseenter', () => {
|
this.canvas.canvas.addEventListener('mouseenter', (e) => {
|
||||||
this.canvas.isMouseOver = true;
|
this.canvas.isMouseOver = true;
|
||||||
|
this.handleMouseEnter(e);
|
||||||
});
|
});
|
||||||
this.canvas.canvas.addEventListener('mouseleave', () => {
|
this.canvas.canvas.addEventListener('mouseleave', (e) => {
|
||||||
this.canvas.isMouseOver = false;
|
this.canvas.isMouseOver = false;
|
||||||
|
this.handleMouseLeave(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,14 +58,14 @@ export class CanvasInteractions {
|
|||||||
handleMouseDown(e) {
|
handleMouseDown(e) {
|
||||||
this.canvas.canvas.focus();
|
this.canvas.canvas.focus();
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
||||||
|
const viewCoords = this.canvas.getMouseViewCoordinates(e);
|
||||||
|
|
||||||
if (this.canvas.maskTool.isActive) {
|
if (this.canvas.maskTool.isActive) {
|
||||||
if (e.button === 1) {
|
if (e.button === 1) {
|
||||||
this.startPanning(e);
|
this.startPanning(e);
|
||||||
this.canvas.render();
|
} else {
|
||||||
return;
|
this.canvas.maskTool.handleMouseDown(worldCoords, viewCoords);
|
||||||
}
|
}
|
||||||
this.canvas.maskTool.handleMouseDown(worldCoords);
|
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -110,6 +112,7 @@ export class CanvasInteractions {
|
|||||||
|
|
||||||
handleMouseMove(e) {
|
handleMouseMove(e) {
|
||||||
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
const worldCoords = this.canvas.getMouseWorldCoordinates(e);
|
||||||
|
const viewCoords = this.canvas.getMouseViewCoordinates(e);
|
||||||
this.canvas.lastMousePosition = worldCoords;
|
this.canvas.lastMousePosition = worldCoords;
|
||||||
|
|
||||||
if (this.canvas.maskTool.isActive) {
|
if (this.canvas.maskTool.isActive) {
|
||||||
@@ -117,8 +120,10 @@ export class CanvasInteractions {
|
|||||||
this.panViewport(e);
|
this.panViewport(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.canvas.maskTool.handleMouseMove(worldCoords);
|
this.canvas.maskTool.handleMouseMove(worldCoords, viewCoords);
|
||||||
if (this.canvas.maskTool.isDrawing) this.canvas.render();
|
if (this.canvas.maskTool.isDrawing) {
|
||||||
|
this.canvas.render();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,13 +153,13 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp(e) {
|
handleMouseUp(e) {
|
||||||
|
const viewCoords = this.canvas.getMouseViewCoordinates(e);
|
||||||
if (this.canvas.maskTool.isActive) {
|
if (this.canvas.maskTool.isActive) {
|
||||||
if (this.interaction.mode === 'panning') {
|
if (this.interaction.mode === 'panning') {
|
||||||
this.resetInteractionState();
|
this.resetInteractionState();
|
||||||
this.canvas.render();
|
} else {
|
||||||
return;
|
this.canvas.maskTool.handleMouseUp(viewCoords);
|
||||||
}
|
}
|
||||||
this.canvas.maskTool.handleMouseUp();
|
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -176,8 +181,12 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMouseLeave(e) {
|
handleMouseLeave(e) {
|
||||||
|
const viewCoords = this.canvas.getMouseViewCoordinates(e);
|
||||||
if (this.canvas.maskTool.isActive) {
|
if (this.canvas.maskTool.isActive) {
|
||||||
this.canvas.maskTool.handleMouseUp();
|
this.canvas.maskTool.handleMouseLeave();
|
||||||
|
if (this.canvas.maskTool.isDrawing) {
|
||||||
|
this.canvas.maskTool.handleMouseUp(viewCoords);
|
||||||
|
}
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -187,6 +196,12 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMouseEnter(e) {
|
||||||
|
if (this.canvas.maskTool.isActive) {
|
||||||
|
this.canvas.maskTool.handleMouseEnter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleWheel(e) {
|
handleWheel(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this.canvas.maskTool.isActive) {
|
if (this.canvas.maskTool.isActive) {
|
||||||
|
|||||||
@@ -20,9 +20,28 @@ export class MaskTool {
|
|||||||
this.isDrawing = false;
|
this.isDrawing = false;
|
||||||
this.lastPosition = null;
|
this.lastPosition = null;
|
||||||
|
|
||||||
|
this.previewCanvas = document.createElement('canvas');
|
||||||
|
this.previewCtx = this.previewCanvas.getContext('2d');
|
||||||
|
this.previewVisible = false;
|
||||||
|
this.previewCanvasInitialized = false;
|
||||||
|
|
||||||
this.initMaskCanvas();
|
this.initMaskCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initPreviewCanvas() {
|
||||||
|
if (this.previewCanvas.parentElement) {
|
||||||
|
this.previewCanvas.parentElement.removeChild(this.previewCanvas);
|
||||||
|
}
|
||||||
|
this.previewCanvas.width = this.canvasInstance.canvas.width;
|
||||||
|
this.previewCanvas.height = this.canvasInstance.canvas.height;
|
||||||
|
this.previewCanvas.style.position = 'absolute';
|
||||||
|
this.previewCanvas.style.left = `${this.canvasInstance.canvas.offsetLeft}px`;
|
||||||
|
this.previewCanvas.style.top = `${this.canvasInstance.canvas.offsetTop}px`;
|
||||||
|
this.previewCanvas.style.pointerEvents = 'none';
|
||||||
|
this.previewCanvas.style.zIndex = '10';
|
||||||
|
this.canvasInstance.canvas.parentElement.appendChild(this.previewCanvas);
|
||||||
|
}
|
||||||
|
|
||||||
setBrushSoftness(softness) {
|
setBrushSoftness(softness) {
|
||||||
this.brushSoftness = Math.max(0, Math.min(1, softness));
|
this.brushSoftness = Math.max(0, Math.min(1, softness));
|
||||||
}
|
}
|
||||||
@@ -42,7 +61,12 @@ export class MaskTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activate() {
|
activate() {
|
||||||
|
if (!this.previewCanvasInitialized) {
|
||||||
|
this.initPreviewCanvas();
|
||||||
|
this.previewCanvasInitialized = true;
|
||||||
|
}
|
||||||
this.isActive = true;
|
this.isActive = true;
|
||||||
|
this.previewCanvas.style.display = 'block';
|
||||||
this.canvasInstance.interaction.mode = 'drawingMask';
|
this.canvasInstance.interaction.mode = 'drawingMask';
|
||||||
if (this.canvasInstance.canvasState && this.canvasInstance.canvasState.maskUndoStack.length === 0) {
|
if (this.canvasInstance.canvasState && this.canvasInstance.canvasState.maskUndoStack.length === 0) {
|
||||||
this.canvasInstance.canvasState.saveMaskState();
|
this.canvasInstance.canvasState.saveMaskState();
|
||||||
@@ -54,6 +78,7 @@ export class MaskTool {
|
|||||||
|
|
||||||
deactivate() {
|
deactivate() {
|
||||||
this.isActive = false;
|
this.isActive = false;
|
||||||
|
this.previewCanvas.style.display = 'none';
|
||||||
this.canvasInstance.interaction.mode = 'none';
|
this.canvasInstance.interaction.mode = 'none';
|
||||||
this.canvasInstance.updateHistoryButtons();
|
this.canvasInstance.updateHistoryButtons();
|
||||||
|
|
||||||
@@ -68,20 +93,33 @@ export class MaskTool {
|
|||||||
this.brushStrength = Math.max(0, Math.min(1, strength));
|
this.brushStrength = Math.max(0, Math.min(1, strength));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseDown(worldCoords) {
|
handleMouseDown(worldCoords, viewCoords) {
|
||||||
if (!this.isActive) return;
|
if (!this.isActive) return;
|
||||||
this.isDrawing = true;
|
this.isDrawing = true;
|
||||||
this.lastPosition = worldCoords;
|
this.lastPosition = worldCoords;
|
||||||
this.draw(worldCoords);
|
this.draw(worldCoords);
|
||||||
|
this.clearPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseMove(worldCoords) {
|
handleMouseMove(worldCoords, viewCoords) {
|
||||||
|
if (this.isActive) {
|
||||||
|
this.drawBrushPreview(viewCoords);
|
||||||
|
}
|
||||||
if (!this.isActive || !this.isDrawing) return;
|
if (!this.isActive || !this.isDrawing) return;
|
||||||
this.draw(worldCoords);
|
this.draw(worldCoords);
|
||||||
this.lastPosition = worldCoords;
|
this.lastPosition = worldCoords;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp() {
|
handleMouseLeave() {
|
||||||
|
this.previewVisible = false;
|
||||||
|
this.clearPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEnter() {
|
||||||
|
this.previewVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseUp(viewCoords) {
|
||||||
if (!this.isActive) return;
|
if (!this.isActive) return;
|
||||||
if (this.isDrawing) {
|
if (this.isDrawing) {
|
||||||
this.isDrawing = false;
|
this.isDrawing = false;
|
||||||
@@ -92,6 +130,7 @@ export class MaskTool {
|
|||||||
if (this.onStateChange) {
|
if (this.onStateChange) {
|
||||||
this.onStateChange();
|
this.onStateChange();
|
||||||
}
|
}
|
||||||
|
this.drawBrushPreview(viewCoords);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +182,28 @@ export class MaskTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawBrushPreview(viewCoords) {
|
||||||
|
if (!this.previewVisible || this.isDrawing) {
|
||||||
|
this.clearPreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clearPreview();
|
||||||
|
const zoom = this.canvasInstance.viewport.zoom;
|
||||||
|
const radius = (this.brushSize / 2) * zoom;
|
||||||
|
|
||||||
|
this.previewCtx.beginPath();
|
||||||
|
this.previewCtx.arc(viewCoords.x, viewCoords.y, radius, 0, 2 * Math.PI);
|
||||||
|
this.previewCtx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
|
||||||
|
this.previewCtx.lineWidth = 1;
|
||||||
|
this.previewCtx.setLineDash([2, 4]);
|
||||||
|
this.previewCtx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearPreview() {
|
||||||
|
this.previewCtx.clearRect(0, 0, this.previewCanvas.width, this.previewCanvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
|
this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height);
|
||||||
if (this.isActive && this.canvasInstance.canvasState) {
|
if (this.isActive && this.canvasInstance.canvasState) {
|
||||||
@@ -176,6 +237,7 @@ export class MaskTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resize(width, height) {
|
resize(width, height) {
|
||||||
|
this.initPreviewCanvas();
|
||||||
const oldMask = this.maskCanvas;
|
const oldMask = this.maskCanvas;
|
||||||
const oldX = this.x;
|
const oldX = this.x;
|
||||||
const oldY = this.y;
|
const oldY = this.y;
|
||||||
|
|||||||
Reference in New Issue
Block a user