diff --git a/frontend/realtime/src/KonvaApp/Engine.ts b/frontend/realtime/src/KonvaApp/Engine.ts index 62326c31d6..136b511fe6 100644 --- a/frontend/realtime/src/KonvaApp/Engine.ts +++ b/frontend/realtime/src/KonvaApp/Engine.ts @@ -128,6 +128,7 @@ export class Engine { nodeTransformerRef: this.nodeTransformer, mediaLayerRef: this.mediaLayer, }); + // Selector Square to select Nodes this.selectorSquare = new SelectorSquare({ captureCanvasRef: this.realTimeDrawEngine.captureCanvas, @@ -178,11 +179,23 @@ export class Engine { // hence, lastly, setup these events this.setupEventSystem(); this.setAppMode(AppModes.SELECT); + } private setAppMode(newAppMode: AppModes) { this.appMode = newAppMode; switch (this.appMode) { + case AppModes.PAINT: { + console.log("APPMODE: PAINT"); + this.selectorSquare.disable(); + this.selectionManager.disable(); + // uiAccess.toolbarMain.enable(); + // uiAccess.toolbarMain.changeButtonState(ToolbarMainButtonNames.SELECT, { + // active: true, + // }); + // this.matteBox.disable(); + return; + } case AppModes.SELECT: { console.log("APPMODE: SELECT"); this.selectorSquare.enable(); @@ -575,6 +588,9 @@ export class Engine { color: nodeColor.color, mediaLayerRef: this.mediaLayer, selectionManagerRef: this.selectionManager, + loaded: async ()=> { + this.realTimeDrawEngine.render(); + } }); newShapeNode.kNode.position(shapeNode.kNode.position()); newShapeNode.kNode.zIndex(shapeNode.kNode.zIndex()); diff --git a/frontend/realtime/src/KonvaApp/RenderingPrimitives/RealTimeDrawEngine.ts b/frontend/realtime/src/KonvaApp/RenderingPrimitives/RealTimeDrawEngine.ts index ab9c1a7731..0de3ec2eb2 100644 --- a/frontend/realtime/src/KonvaApp/RenderingPrimitives/RealTimeDrawEngine.ts +++ b/frontend/realtime/src/KonvaApp/RenderingPrimitives/RealTimeDrawEngine.ts @@ -26,6 +26,7 @@ export class RealTimeDrawEngine { // capturing composite within window private mediaLayerRef: Konva.Layer; + private drawingsLayer: Konva.Layer; // New Layer for Drawings private height: number; private width: number; @@ -69,7 +70,8 @@ export class RealTimeDrawEngine { this.positionX = window.innerWidth / 2 - this.width / 2 - this.width; this.positionY = window.innerHeight / 2 - this.height / 2; - this.positionPreviewX = window.innerWidth / 2 - this.width / 2 + this.width; + this.positionPreviewX = + window.innerWidth / 2 - this.width / 2 + this.width; this.positionPreviewY = window.innerHeight / 2 - this.height / 2; this.offScreenCanvas = offScreenCanvas; @@ -79,6 +81,12 @@ export class RealTimeDrawEngine { // this is the whole canvas this.mediaLayerRef = mediaLayerRef; + // Create a separate layer for drawings + this.drawingsLayer = new Konva.Layer({ + clearBeforeDraw: true, // Ensures transparent background + }); + this.mediaLayerRef.getStage()?.add(this.drawingsLayer); + // Set background layer to red and media layer to green for visibility this.port = undefined; @@ -120,18 +128,157 @@ export class RealTimeDrawEngine { this.previewCanvas.setZIndex(1); // Add mouse events for preview canvas copying //this.previewCopyListener(); + //this.paintMode(); + } + public paintMode() { + let isDrawing = false; + let currentLine: Konva.Line | null = null; + + const startDrawing = (pos: { x: number; y: number }) => { + const stage = this.mediaLayerRef.getStage(); + if (!stage) return; + + // Convert pointer position to relative position within capture canvas + const captureBox = this.captureCanvas.getClientRect(); + const relativeX = pos.x - captureBox.x; + const relativeY = pos.y - captureBox.y; + + currentLine = new Konva.Line({ + points: [relativeX, relativeY], + stroke: "#df4b26", + strokeWidth: 5, + lineCap: "round", + lineJoin: "round", + x: this.captureCanvas.x(), + y: this.captureCanvas.y(), + draggable: false, + }); + + this.drawingsLayer.add(currentLine); // Add to drawingsLayer + isDrawing = true; + }; + + const draw = (pos: { x: number; y: number }) => { + if (!isDrawing || !currentLine) return; + + // Convert pointer position to relative position + const captureBox = this.captureCanvas.getClientRect(); + const relativeX = pos.x - captureBox.x; + const relativeY = pos.y - captureBox.y; + + const newPoints = currentLine.points().concat([relativeX, relativeY]); + currentLine.points(newPoints); + this.drawingsLayer.batchDraw(); + }; + + const stopDrawing = () => { + if (!isDrawing || !currentLine) return; + + // Store current line before resetting state + const lineToConvert = currentLine; + + // Reset drawing state immediately so we can start a new stroke + isDrawing = false; + currentLine = null; + + // Create a temporary layer to render the line + const tempLayer = new Konva.Layer({ + clearBeforeDraw: true, + }); + this.mediaLayerRef.getStage()?.add(tempLayer); + tempLayer.add(lineToConvert); + + // Get the line canvas with transparent background + // Get the bounding box of the line + const lineBounds = lineToConvert.getClientRect(); + + // Create canvas with just enough size to contain the line + const lineCanvas = tempLayer.toCanvas({ + x: lineBounds.x, + y: lineBounds.y, + width: lineBounds.width, + height: lineBounds.height, + pixelRatio: 1, + }); + + // Create a new Konva Image with the drawn content + const drawingImage = new Konva.Image({ + x: lineBounds.x, + y: lineBounds.y, + width: lineBounds.width, + height: lineBounds.height, + image: lineCanvas, + listening: false, // Disable event listening to allow captureCanvas to receive events + draggable: true, + stroke: '#000000', + strokeWidth: 2, + dash: [5, 5], // Creates dotted outline + globalCompositeOperation: 'source-over', + fill: 'green' // Add green background + }); + + // Add the image to the drawingsLayer + this.drawingsLayer.add(drawingImage); + + // Clean up + lineToConvert.destroy(); + tempLayer.destroy(); + + this.drawingsLayer.batchDraw(); + }; + + // Check if point is within capture canvas bounds + const isWithinCaptureCanvas = (pos: { x: number; y: number }) => { + const captureBox = this.captureCanvas.getClientRect(); + return ( + pos.x >= captureBox.x && + pos.x <= captureBox.x + captureBox.width && + pos.y >= captureBox.y && + pos.y <= captureBox.y + captureBox.height + ); + }; + + // Add event listeners + const stage = this.mediaLayerRef.getStage(); + if (!stage) return; + + stage.on("mousedown touchstart", (e) => { + const pos = stage.getPointerPosition(); + if (pos && isWithinCaptureCanvas(pos)) { + startDrawing(pos); + } + }); + + stage.on("mousemove touchmove", (e) => { + const pos = stage.getPointerPosition(); + if (pos && isWithinCaptureCanvas(pos)) { + draw(pos); + } + }); + + stage.on("mouseup touchend", async () => { + stopDrawing(); + await this.render(); + }); + + // Return cleanup function + return () => { + stage.off("mousedown touchstart"); + stage.off("mousemove touchmove"); + stage.off("mouseup touchend"); + }; } public previewCopyListener() { - this.previewCanvas.on('mousedown touchstart', () => { + this.previewCanvas.on("mousedown touchstart", () => { if (!this.outputBitmap) { - console.log("No preview image to copy"); + console.log("No preview image to copy"); return; } - + // TODO create it as a node. - + // const imageNode = new ImageNode({ // mediaLayerRef: this.mediaLayer, // canvasPosition: this.renderEngine.captureCanvas.position(), @@ -148,36 +295,35 @@ export class RealTimeDrawEngine { height: this.height, image: this.outputBitmap, draggable: true, - listening: true + listening: true, }); - this.mediaLayerRef.add(previewCopy); - previewCopy.moveToTop(); - this.mediaLayerRef.draw(); + this.drawingsLayer.add(previewCopy); // Add to drawingsLayer instead of mediaLayerRef + this.drawingsLayer.batchDraw(); // Start dragging immediately previewCopy.startDrag(); // Handle drag events - previewCopy.on('dragmove', () => { - this.mediaLayerRef.draw(); + previewCopy.on("dragmove", () => { + this.drawingsLayer.draw(); }); - previewCopy.on('dragend', () => { + previewCopy.on("dragend", () => { const previewBox = previewCopy.getClientRect(); const captureBox = this.captureCanvas.getClientRect(); - + if (Konva.Util.haveIntersection(previewBox, captureBox)) { // Snap to capture canvas position previewCopy.position({ x: this.captureCanvas.x(), - y: this.captureCanvas.y() + y: this.captureCanvas.y(), }); - this.mediaLayerRef.batchDraw(); + this.drawingsLayer.batchDraw(); } else { // Remove if dropped outside capture area previewCopy.destroy(); - this.mediaLayerRef.batchDraw(); + this.drawingsLayer.batchDraw(); } }); });