mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 09:22:41 -04:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e99baccdb2 | ||
|
|
f821d12cdf | ||
|
|
ffd52845e5 | ||
|
|
4aece4045f | ||
|
|
b08731305d | ||
|
|
ad078ed9f4 | ||
|
|
8250ea0b4a | ||
|
|
fb87cdaee4 | ||
|
|
0f8e7dae82 | ||
|
|
5668a43fc5 |
@@ -2,6 +2,7 @@ import Konva from "konva";
|
||||
|
||||
import { uiAccess, uiEvents } from "~/signals";
|
||||
import { ShapeNode } from "./Nodes";
|
||||
import { PaintNode } from "./Nodes/PaintNode";
|
||||
|
||||
import { UndoStackManager } from "./UndoRedo";
|
||||
import { CommandManager, MatteBox, SceneManager } from "./EngineUtitlities";
|
||||
@@ -110,17 +111,8 @@ export class Engine {
|
||||
});
|
||||
|
||||
// core layer for all the work done.
|
||||
|
||||
this.offScreenCanvas = new OffscreenCanvas(0, 0);
|
||||
|
||||
this.realTimeDrawEngine = new RealTimeDrawEngine({
|
||||
width: VideoResolutions.SQUARE_1024.width,
|
||||
height: VideoResolutions.SQUARE_1024.height,
|
||||
mediaLayerRef: this.mediaLayer,
|
||||
bgLayerRef: this.bgLayer,
|
||||
offScreenCanvas: this.offScreenCanvas,
|
||||
});
|
||||
|
||||
// Collection of all Nodes
|
||||
this.nodesManager = new NodesManager();
|
||||
// Partial Collection of selected Nodes
|
||||
@@ -128,6 +120,18 @@ export class Engine {
|
||||
nodeTransformerRef: this.nodeTransformer,
|
||||
mediaLayerRef: this.mediaLayer,
|
||||
});
|
||||
|
||||
this.realTimeDrawEngine = new RealTimeDrawEngine({
|
||||
width: VideoResolutions.SQUARE_1024.width,
|
||||
height: VideoResolutions.SQUARE_1024.height,
|
||||
mediaLayerRef: this.mediaLayer,
|
||||
offScreenCanvas: this.offScreenCanvas,
|
||||
onDraw:async (canvas, lineBounds) => {
|
||||
await this.addPaintNode(canvas, lineBounds);
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
// Selector Square to select Nodes
|
||||
this.selectorSquare = new SelectorSquare({
|
||||
captureCanvasRef: this.realTimeDrawEngine.captureCanvas,
|
||||
@@ -177,14 +181,31 @@ export class Engine {
|
||||
// some of the managers has events
|
||||
// hence, lastly, setup these events
|
||||
this.setupEventSystem();
|
||||
this.setAppMode(AppModes.SELECT);
|
||||
this.setAppMode(this.appMode);
|
||||
}
|
||||
|
||||
private setAppMode(newAppMode: AppModes) {
|
||||
this.appMode = newAppMode;
|
||||
switch (this.appMode) {
|
||||
case AppModes.PAINT: {
|
||||
console.log("APPMODE: PAINT");
|
||||
this.selectorSquare.disable(); // this breaks the paint if you use it in the wrong spot
|
||||
this.selectionManager.disable(); // this prevents selection.
|
||||
this.realTimeDrawEngine.enablePaintMode();
|
||||
this.realTimeDrawEngine.disableDragging();
|
||||
|
||||
uiAccess.toolbarMain.enable();
|
||||
uiAccess.toolbarMain.changeButtonState(ToolbarMainButtonNames.SELECT, {
|
||||
active: false,
|
||||
});
|
||||
this.matteBox.disable();
|
||||
return;
|
||||
}
|
||||
case AppModes.SELECT: {
|
||||
console.log("APPMODE: SELECT");
|
||||
this.realTimeDrawEngine.disablePaintMode();
|
||||
this.realTimeDrawEngine.enableDragging();
|
||||
|
||||
this.selectorSquare.enable();
|
||||
this.selectionManager.enable();
|
||||
uiAccess.toolbarMain.enable();
|
||||
@@ -358,21 +379,24 @@ export class Engine {
|
||||
// Listen to Toolbar Main
|
||||
uiEvents.toolbarMain.UNDO.onClick(() => this.undoStackManager.undo());
|
||||
uiEvents.toolbarMain.REDO.onClick(() => this.undoStackManager.redo());
|
||||
|
||||
|
||||
uiEvents.toolbarMain.SAVE.onClick(async (/*event*/) => {
|
||||
await this.realTimeDrawEngine.saveOutput();
|
||||
});
|
||||
|
||||
uiEvents.toolbarMain.SELECT.onClick(() => {
|
||||
console.log("Toolbar Main >> Select");
|
||||
this.setAppMode(AppModes.SELECT)
|
||||
});
|
||||
|
||||
uiEvents.toolbarMain.PAINT.onClick(() => {
|
||||
console.log("Toolbar Main >> Paint");
|
||||
this.setAppMode(AppModes.PAINT)
|
||||
});
|
||||
|
||||
uiEvents.toolbarMain.ERASER.onClick(() => {
|
||||
console.log("Toolbar Main >> Eraser");
|
||||
|
||||
});
|
||||
|
||||
uiEvents.toolbarMain.PREVIEW.onClick(async () => {
|
||||
@@ -443,6 +467,7 @@ export class Engine {
|
||||
});
|
||||
|
||||
uiEvents.onAddShapeToEngine((shapeData) => {
|
||||
this.setAppMode(AppModes.SELECT);
|
||||
switch (shapeData.shape) {
|
||||
case "circle":
|
||||
this.addShape(ShapeType.CIRCLE, 100);
|
||||
@@ -468,6 +493,9 @@ export class Engine {
|
||||
}
|
||||
this.changeNodeColor(nodeColor);
|
||||
});
|
||||
uiEvents.toolbarMain.onPaintColorChanged((color)=> {
|
||||
this.realTimeDrawEngine.paintColor = color;
|
||||
});
|
||||
}
|
||||
|
||||
disableAllButtons() {
|
||||
@@ -575,6 +603,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());
|
||||
@@ -618,7 +649,6 @@ export class Engine {
|
||||
console.debug("Added shapenode:", shapeNode);
|
||||
console.debug("Added node's ID:", shapeNode.kNode._id);
|
||||
|
||||
this.realTimeDrawEngine.addNodes(shapeNode);
|
||||
}
|
||||
|
||||
public addImage(imageFile: File) {
|
||||
@@ -635,8 +665,22 @@ export class Engine {
|
||||
imageNode.kNode.zIndex(1);
|
||||
this.commandManager.createNode(imageNode);
|
||||
|
||||
this.realTimeDrawEngine.addNodes(imageNode);
|
||||
}
|
||||
public addPaintNode(canvas: HTMLCanvasElement,lineBounds:{
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;}) {
|
||||
var node = new PaintNode({
|
||||
canvasElement:canvas,
|
||||
lineBounds:lineBounds,
|
||||
mediaLayerRef: this.mediaLayer,
|
||||
selectionManagerRef: this.selectionManager,
|
||||
loaded: async () => {
|
||||
await this.realTimeDrawEngine.render()
|
||||
}})
|
||||
this.commandManager.createNode(node);
|
||||
}
|
||||
|
||||
public addVideo(
|
||||
videNodeData: Partial<VideoNodeData> & { mediaFileUrl: string },
|
||||
|
||||
@@ -20,14 +20,15 @@ import {
|
||||
NodeTransformer,
|
||||
SelectionManager,
|
||||
} from "../NodesManagers";
|
||||
import { RenderEngine } from "../RenderingPrimitives/RenderEngine";
|
||||
import { RealTimeDrawEngine } from "../RenderingPrimitives/RealTimeDrawEngine";
|
||||
import { PaintNode } from "../Nodes/PaintNode";
|
||||
|
||||
interface EngineReferences {
|
||||
mediaLayerRef: Konva.Layer;
|
||||
nodesManagerRef: NodesManager;
|
||||
nodeTransformerRef: NodeTransformer;
|
||||
selectionManagerRef: SelectionManager;
|
||||
renderEngineRef: RenderEngine;
|
||||
renderEngineRef: RealTimeDrawEngine;
|
||||
undoStackManagerRef: UndoStackManager;
|
||||
}
|
||||
|
||||
@@ -36,7 +37,7 @@ export class CommandManager {
|
||||
private nodesManagerRef: NodesManager;
|
||||
private nodeTransformerRef: NodeTransformer;
|
||||
private selectionManagerRef: SelectionManager;
|
||||
private renderEngineRef: RenderEngine;
|
||||
private renderEngineRef: RealTimeDrawEngine;
|
||||
private undoStackManagerRef: UndoStackManager;
|
||||
|
||||
constructor(engineRefs: EngineReferences) {
|
||||
@@ -48,7 +49,8 @@ export class CommandManager {
|
||||
this.undoStackManagerRef = engineRefs.undoStackManagerRef;
|
||||
}
|
||||
|
||||
createNode(node: VideoNode | ImageNode | TextNode | ShapeNode) {
|
||||
createNode(node: MediaNode) {
|
||||
|
||||
const command = new CreateCommand({
|
||||
nodes: new Set<MediaNode>([node]),
|
||||
mediaLayerRef: this.mediaLayerRef,
|
||||
@@ -62,6 +64,7 @@ export class CommandManager {
|
||||
// Set the kNode's manual id to same as kNode's internal _id
|
||||
// This helps identify the kNode in the Konva layer with find
|
||||
node.kNode.id(node.kNode._id.toString());
|
||||
this.renderEngineRef.render();
|
||||
}
|
||||
deleteNodes() {
|
||||
const nodes = this.selectionManagerRef.getSelectedNodes();
|
||||
@@ -74,6 +77,7 @@ export class CommandManager {
|
||||
renderEngineRef: this.renderEngineRef,
|
||||
});
|
||||
this.undoStackManagerRef.executeCommand(command);
|
||||
this.renderEngineRef.render();
|
||||
}
|
||||
toggleLockNodes() {
|
||||
const nodes = this.selectionManagerRef.getSelectedNodes();
|
||||
@@ -101,6 +105,7 @@ export class CommandManager {
|
||||
mediaLayerRef: this.mediaLayerRef,
|
||||
});
|
||||
this.undoStackManagerRef.executeCommand(command);
|
||||
this.renderEngineRef.render();
|
||||
}
|
||||
moveNodesDown() {
|
||||
const command = new MoveLayerDown({
|
||||
@@ -109,6 +114,7 @@ export class CommandManager {
|
||||
mediaLayerRef: this.mediaLayerRef,
|
||||
});
|
||||
this.undoStackManagerRef.executeCommand(command);
|
||||
this.renderEngineRef.render();
|
||||
}
|
||||
translateNodes(props: {
|
||||
nodes: Set<MediaNode>;
|
||||
@@ -120,6 +126,7 @@ export class CommandManager {
|
||||
layerRef: this.mediaLayerRef,
|
||||
});
|
||||
this.undoStackManagerRef.pushCommand(command);
|
||||
|
||||
}
|
||||
transformNodes(props: {
|
||||
nodes: Set<MediaNode>;
|
||||
@@ -131,6 +138,7 @@ export class CommandManager {
|
||||
layerRef: this.mediaLayerRef,
|
||||
});
|
||||
this.undoStackManagerRef.pushCommand(command);
|
||||
this.renderEngineRef.render();
|
||||
}
|
||||
addChromaKey(props: { videoNode: VideoNode; newChromaColor: RGBColor }) {
|
||||
const command = new AddChromaKey(props);
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import Konva from "konva";
|
||||
import { SelectionManager } from "../NodesManagers";
|
||||
import { Position, NodeData } from "../types";
|
||||
import { NodeType } from "./constants";
|
||||
|
||||
import { NetworkedNode } from "./NetworkedNode";
|
||||
|
||||
interface PaintNodeConstructor {
|
||||
canvasElement:HTMLCanvasElement;
|
||||
lineBounds: {
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
mediaLayerRef: Konva.Layer;
|
||||
selectionManagerRef: SelectionManager;
|
||||
|
||||
loaded: () => Promise<void>;
|
||||
}
|
||||
|
||||
export class PaintNode extends NetworkedNode {
|
||||
retry(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
constructor({
|
||||
canvasElement,
|
||||
lineBounds,
|
||||
mediaLayerRef,
|
||||
selectionManagerRef,
|
||||
loaded,
|
||||
}: PaintNodeConstructor) {
|
||||
|
||||
const cl = canvasElement
|
||||
|
||||
// Convert circle to image
|
||||
const dataURL = cl.toDataURL();
|
||||
const imageObj = new Image();
|
||||
imageObj.src = dataURL;
|
||||
|
||||
const paintShape = new Konva.Image({
|
||||
x: lineBounds.x,
|
||||
y: lineBounds.y,
|
||||
width: lineBounds.width,
|
||||
height: lineBounds.height,
|
||||
image: imageObj,
|
||||
listening: false, // Disable event listening to allow captureCanvas to receive events
|
||||
draggable: true,
|
||||
// stroke: '#000000',
|
||||
// strokeWidth: 2,
|
||||
// dash: [5, 5], // Creates dotted outline THIS IS FOR DEBUG
|
||||
globalCompositeOperation: 'source-over',
|
||||
fill: 'transparent'
|
||||
});
|
||||
|
||||
imageObj.onload = () => {
|
||||
this.mediaLayerRef.draw();
|
||||
loaded();
|
||||
};
|
||||
|
||||
super({
|
||||
selectionManagerRef: selectionManagerRef,
|
||||
mediaLayerRef: mediaLayerRef,
|
||||
kNode: paintShape,
|
||||
});
|
||||
|
||||
this.mediaLayerRef.add(this.kNode);
|
||||
this.listenToBaseKNode();
|
||||
this.mediaLayerRef.draw();
|
||||
}
|
||||
|
||||
public getNodeData(canvasPosition: Position) {
|
||||
const data: NodeData = {
|
||||
type: NodeType.SHAPE,
|
||||
transform: {
|
||||
position: {
|
||||
x: this.kNode.position().x - canvasPosition.x,
|
||||
y: this.kNode.position().y - canvasPosition.y,
|
||||
},
|
||||
size: this.kNode.size(),
|
||||
rotation: this.kNode.rotation(),
|
||||
scale: {
|
||||
x: this.kNode.scaleX(),
|
||||
y: this.kNode.scaleY(),
|
||||
},
|
||||
zIndex: this.kNode.getZIndex(),
|
||||
},
|
||||
};
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,15 @@ import { MediaNode } from "../types";
|
||||
|
||||
import { RenderTask } from "./RenderTask";
|
||||
import { OffScreenSceneCanvas } from "./OffScreenSceneCanvas";
|
||||
import { Image } from "@tauri-apps/api/image";
|
||||
|
||||
import { PaintNode } from "../Nodes/PaintNode";
|
||||
|
||||
// https://www.aiseesoft.com/resource/phone-aspect-ratio-screen-resolution.html#:~:text=16%3A9%20Aspect%20Ratio
|
||||
|
||||
export class RealTimeDrawEngine {
|
||||
private videoNodes: VideoNode[];
|
||||
private imageNodes: (ImageNode | TextNode | ShapeNode)[];
|
||||
private imageNodes: (ImageNode | TextNode | ShapeNode | PaintNode)[];
|
||||
|
||||
private offScreenCanvas: OffscreenCanvas;
|
||||
private outputBitmap: ImageBitmap | undefined;
|
||||
@@ -26,6 +29,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;
|
||||
@@ -46,20 +50,40 @@ export class RealTimeDrawEngine {
|
||||
public currentPrompt: string;
|
||||
public currentStrength: number;
|
||||
|
||||
// Paint Color
|
||||
// paint Brush Size
|
||||
// has to exit out of paint mode when shape or image are used.
|
||||
public paintColor: string = "#000000";
|
||||
public paintBrushSize: number = 5;
|
||||
public isPaintMode: boolean = false;
|
||||
|
||||
private onDrawCallback?: (canvas: HTMLCanvasElement,lineBounds:{
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;}) => void;
|
||||
|
||||
constructor({
|
||||
width,
|
||||
height,
|
||||
mediaLayerRef,
|
||||
offScreenCanvas,
|
||||
onDraw,
|
||||
}: {
|
||||
width: number;
|
||||
height: number;
|
||||
mediaLayerRef: Konva.Layer;
|
||||
offScreenCanvas: OffscreenCanvas;
|
||||
onDraw?: (canvas: HTMLCanvasElement,lineBounds:{
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;}) => void;
|
||||
}) {
|
||||
this.videoLoadingCanvas = undefined;
|
||||
this.videoNodes = [];
|
||||
this.imageNodes = [];
|
||||
this.onDrawCallback = onDraw
|
||||
|
||||
// TODO: Make this dynamic and update this on change of canvas.
|
||||
|
||||
@@ -69,7 +93,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 +104,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);// to od pass in stage
|
||||
|
||||
// Set background layer to red and media layer to green for visibility
|
||||
|
||||
this.port = undefined;
|
||||
@@ -123,15 +154,178 @@ export class RealTimeDrawEngine {
|
||||
|
||||
}
|
||||
|
||||
private isEnabled: boolean = false;
|
||||
private cleanupFunction: (() => void) | null = null;
|
||||
|
||||
public paintMode() {
|
||||
let isDrawing = false;
|
||||
let currentLine: Konva.Line | null = null;
|
||||
|
||||
const startDrawing = (pos: { x: number; y: number }) => {
|
||||
if (!this.isEnabled) return;
|
||||
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: this.paintColor,
|
||||
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 (!this.isEnabled) return;
|
||||
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 (!this.isEnabled) return;
|
||||
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,
|
||||
});
|
||||
|
||||
if (this.onDrawCallback) {
|
||||
this.onDrawCallback(lineCanvas, lineBounds);
|
||||
}
|
||||
|
||||
// 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();
|
||||
});
|
||||
|
||||
// Store cleanup function
|
||||
this.cleanupFunction = () => {
|
||||
stage.off("mousedown touchstart");
|
||||
stage.off("mousemove touchmove");
|
||||
stage.off("mouseup touchend");
|
||||
};
|
||||
}
|
||||
|
||||
public enablePaintMode() {
|
||||
this.isEnabled = true;
|
||||
if (!this.cleanupFunction) {
|
||||
this.paintMode();
|
||||
}
|
||||
}
|
||||
|
||||
public enableDragging() {
|
||||
// Enable dragging for all nodes in media layer
|
||||
this.imageNodes?.forEach((node) => {
|
||||
node.kNode.draggable(true);
|
||||
node.kNode.listening(true);
|
||||
});
|
||||
this.mediaLayerRef.batchDraw();
|
||||
}
|
||||
|
||||
public disableDragging() {
|
||||
// Disable dragging for all nodes in media layer
|
||||
this.imageNodes?.forEach((node) => {
|
||||
node.kNode.draggable(false);
|
||||
node.kNode.listening(false);
|
||||
});
|
||||
this.mediaLayerRef.batchDraw();
|
||||
}
|
||||
|
||||
public disablePaintMode() {
|
||||
this.isEnabled = false;
|
||||
if (this.cleanupFunction) {
|
||||
this.cleanupFunction();
|
||||
this.cleanupFunction = null;
|
||||
}
|
||||
}
|
||||
|
||||
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,42 +342,41 @@ 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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public findImageNodeById(id: string): (ImageNode | TextNode | ShapeNode | undefined) {
|
||||
public findImageNodeById(id: string): (ImageNode | TextNode | ShapeNode | PaintNode | undefined) {
|
||||
return this.imageNodes.find(node => {
|
||||
if (node.kNode) {
|
||||
return node.kNode.id() === id;
|
||||
@@ -336,14 +529,23 @@ export class RealTimeDrawEngine {
|
||||
};
|
||||
|
||||
public async addNodes(node: MediaNode) {
|
||||
|
||||
if (node instanceof ImageNode || node instanceof TextNode || node instanceof ShapeNode) {
|
||||
if (node instanceof ImageNode ||
|
||||
node instanceof TextNode ||
|
||||
node instanceof ShapeNode ||
|
||||
node instanceof PaintNode)
|
||||
{
|
||||
console.debug("Adding node:", node);
|
||||
this.imageNodes.push(node);
|
||||
console.log(this.imageNodes)
|
||||
node.kNode.on("dragend", this.handleNodeDragEnd);
|
||||
}
|
||||
|
||||
|
||||
// ensure the layer doesn't move if added while painting.
|
||||
if (this.isEnabled) {
|
||||
this.disableDragging();
|
||||
}
|
||||
|
||||
await this.render();
|
||||
}
|
||||
|
||||
public removeNodes(node: MediaNode) {
|
||||
@@ -353,7 +555,7 @@ export class RealTimeDrawEngine {
|
||||
node.kNode.off("dragend", this.handleNodeDragEnd);
|
||||
this.videoNodes.splice(index, 1);
|
||||
}
|
||||
} else if (node instanceof ImageNode || node instanceof TextNode) {
|
||||
} else if (node instanceof ImageNode || node instanceof TextNode || node instanceof ShapeNode || node instanceof PaintNode) {
|
||||
const index = this.imageNodes.indexOf(node);
|
||||
if (index > -1) {
|
||||
node.kNode.off("dragend", this.handleNodeDragEnd);
|
||||
@@ -536,12 +738,12 @@ export class RealTimeDrawEngine {
|
||||
})) as ImageBitmap;
|
||||
|
||||
// Test code
|
||||
if (true) {
|
||||
this.outputBitmap = bitmap;
|
||||
this.previewCanvas.image(bitmap);
|
||||
this.isProcessing = false;
|
||||
return;
|
||||
}
|
||||
// if (true) {
|
||||
// this.outputBitmap = bitmap;
|
||||
// this.previewCanvas.image(bitmap);
|
||||
// this.isProcessing = false;
|
||||
// return;
|
||||
// }
|
||||
|
||||
try {
|
||||
const base64Bitmap = await this.imageBitmapToBase64(bitmap);
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
NodeTransformer,
|
||||
SelectionManager,
|
||||
} from "../NodesManagers";
|
||||
import { RenderEngine } from "../RenderingPrimitives/RenderEngine";
|
||||
import { RealTimeDrawEngine } from "../RenderingPrimitives/RealTimeDrawEngine";
|
||||
|
||||
import { toolbarNode } from "~/signals/uiAccess/toolbarNode";
|
||||
|
||||
@@ -19,7 +19,7 @@ export class CreateCommand implements ICommand {
|
||||
private nodeTransformerRef: NodeTransformer;
|
||||
private selectionManagerRef: SelectionManager;
|
||||
|
||||
private renderEngineRef: RenderEngine;
|
||||
private renderEngineRef: RealTimeDrawEngine;
|
||||
|
||||
constructor({
|
||||
nodes,
|
||||
@@ -33,7 +33,7 @@ export class CreateCommand implements ICommand {
|
||||
mediaLayerRef: Konva.Layer;
|
||||
nodesManagerRef: NodesManager;
|
||||
nodeTransformerRef: NodeTransformer;
|
||||
renderEngineRef: RenderEngine;
|
||||
renderEngineRef: RealTimeDrawEngine;
|
||||
selectionManagerRef: SelectionManager;
|
||||
}) {
|
||||
this.nodes = nodes;
|
||||
@@ -53,6 +53,7 @@ export class CreateCommand implements ICommand {
|
||||
this.renderEngineRef.addNodes(node);
|
||||
});
|
||||
this.mediaLayerRef.draw();
|
||||
|
||||
}
|
||||
|
||||
undo() {
|
||||
@@ -71,5 +72,7 @@ export class CreateCommand implements ICommand {
|
||||
this.nodeTransformerRef.clear();
|
||||
toolbarNode.hide();
|
||||
this.mediaLayerRef.draw();
|
||||
this.renderEngineRef.render();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@ import { NodeType, BaseNode, ImageNode, TextNode, VideoNode, ShapeNode } from ".
|
||||
import { TransformationData } from "./Transformation";
|
||||
|
||||
import { TextNodeData } from "./Text";
|
||||
import { PaintNode } from "../Nodes/PaintNode";
|
||||
|
||||
export type MediaNode =
|
||||
// | NetworkedNode
|
||||
BaseNode | VideoNode | ImageNode | TextNode | ShapeNode;
|
||||
BaseNode | VideoNode | ImageNode | TextNode | ShapeNode | PaintNode;
|
||||
|
||||
export type ImageNodeData = {
|
||||
mediaFileUrl: string;
|
||||
|
||||
@@ -13,9 +13,6 @@ import {
|
||||
import { paperWrapperStyles, toolTipStyles } from "~/components/styles";
|
||||
import { ToolbarNodeButtonNames } from "./enums";
|
||||
import { ToolbarNodeButtonData } from "./data";
|
||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import { Fragment } from "react/jsx-runtime";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ColorPicker } from "~/components/ui/TextEditor/ColorPicker";
|
||||
|
||||
export interface ToolbarNodeProps {
|
||||
|
||||
Reference in New Issue
Block a user