canvas-compositor/Compositor/Node.ts

416 lines
10 KiB
TypeScript

type DrawArea = {width:number,height:number};
type DrawFunction<T> = (this: T, ctx: CanvasBuffer,area:DrawArea) => any;
import CanvasBuffer from "./CanvasBuffer";
import NodeEvent from "./NodeEvent";
export default class Node
{
public name = "";
public context : CanvasBuffer = new CanvasBuffer();
protected buffered : boolean = false;
protected requiredRedraw : boolean = true;
public ChildNodes : Node[] = [];
public parentNode? : Node;
public width : number = 0;
public height : number = 0;
public x : number = 0;
public y : number = 0;
public scale : number = 1;
public rotate = 0;
public mouseHover : boolean = false;
public padding = {
left: 0,
right: 0,
top: 0,
bottom: 0
};
constructor(name?:string)
{
this.name = name || "";
}
public update()
{
this.requiredRedraw = true;
if(this.parentNode)
{
this.parentNode.update();
}
else
{
this.emit("draw", new NodeEvent(), false);
}
}
public draw(maxWidth?: number, maxHeight?: number) : CanvasBuffer
{
this.context.clear();
let drawableAreaWidth = this.width;
let drawableAreaHeight = this.height;
if(maxWidth)
{
drawableAreaWidth = Math.min(drawableAreaWidth, maxWidth);
}
if(maxHeight)
{
drawableAreaHeight = Math.min(drawableAreaHeight, maxHeight);
}
if(this.hasEvent("draw"))
{
let cancelDraw = false;
this.handleEvent("draw:before",(callback) => {
return callback.call(this,{
drawableAreaWidth,
drawableAreaHeight
})
});
let frame = this.handleEvent("draw",(event) => {
let offscreen = new CanvasBuffer(drawableAreaWidth, drawableAreaHeight);
cancelDraw = event.apply(this, [
offscreen, {
width: drawableAreaWidth,
height: drawableAreaHeight
}
]);
return offscreen;
}) as CanvasBuffer;
this.context = frame;
this.buffered = true;
this.handleEvent("draw:after",(callback) => {
return callback.call(this,{
drawableAreaWidth,
drawableAreaHeight
})
});
if(!cancelDraw)
{
return frame;
}
};
let width = drawableAreaWidth - (this.padding.right + this.padding.left);
let height = drawableAreaHeight - (this.padding.bottom + this.padding.top);
this.buffered = true;
let offscreen = new CanvasBuffer(width, height);
for (const node of this.ChildNodes)
{
this.DrawChild(
offscreen,
node,
width, height
);
};
offscreen.writeTo(
this.context,
0, 0,
offscreen.width, offscreen.height,
this.padding.left, this.padding.top,
offscreen.width, offscreen.height
);
return this.context;
}
public getContextArea()
{
return {
width: Math.min(
this.width,
this.parentNode?.width || Infinity
) - (
this.padding.right + this.padding.left
),
height: Math.min(
this.height,
this.parentNode?.height || Infinity
) - (
this.padding.bottom + this.padding.top
)
};
}
public DrawChild(canvas:CanvasBuffer, node:Node, maxWidth: number, maxHeight: number)
{
let canvasWidth = Math.min(node.width, maxWidth);
let canvasHeight = Math.min(node.height, maxHeight);
let x = 0;
let y = 0;
node.handleEvent("draw:before",(callback) => {
return callback.call(node,{
canvasWidth,
canvasHeight
})
});
if(node.width == 0 || node.height == 0)
{
return;
}
canvasWidth = Math.min(node.width, maxWidth);
canvasHeight = Math.min(node.height, maxHeight);
x += node.x;
y += node.y;
let frame : CanvasBuffer;
if(!node.buffered)
{
frame = node.draw(canvasWidth, canvasHeight);
}
else
{
if(node.requiredRedraw)
{
frame = node.draw(canvasWidth, canvasHeight);
}
else
{
frame = node.context as CanvasBuffer;
}
};
let context = canvas.context;
context.save();
if(node.rotate != 0)
{
let translateX = canvasWidth / 2;
let translateY = canvasHeight / 2;
translateX += x;
translateY += y;
context.translate(translateX, translateY);
context.rotate(node.rotate);
context.translate(-translateX, -translateY);
}
canvas.writeFrom(
frame.offscreen,
0,
0,
frame.offscreen.width,
frame.offscreen.height,
x,
y,
canvasWidth * node.scale,
canvasHeight * node.scale
)
node.requiredRedraw = false;
context.restore();
node.handleEvent("draw:after",(callback) => {
return callback.call(this,{
canvasWidth,
canvasHeight
})
});
}
public isMatchPoint(x:number, y: number)
{
let {
x:realX,
y:realY,
width:realWidth,
height:realheight
} = this.realOffset();
let top = realY;
let left = realX;
let right = realX + realWidth;
let bottom = realY + realheight;
if(
top < y &&
bottom > y &&
left < x &&
right > x
)
{
return true;
}
return false;
}
protected _events : Map<
string,
(
(
this:Node,
e:NodeEvent
) => any
)[]
> = new Map();
public on(eventName:string, callback: ((this:Node,e:NodeEvent) => any))
{
if(this._events.has(eventName))
{
this._events.get(eventName)?.push(callback);
}else{
this._events.set(eventName,[callback]);
}
}
public realOffset() : {x:number,y:number,width: number, height: number}
{
let {
width,
height
} = this.getContextArea();
if(this.parentNode)
{
let current : Node = this;
let x = 0;
let y = 0;
while(1)
{
x += current.x;
y += current.y;
if(current.parentNode)
{
x += current.parentNode.padding.left;
y += current.parentNode.padding.top;
current = current.parentNode;
}
else
{
break;
}
};
return {
x,
y,
width,
height
}
}else{
let x = this.x;
let y = this.y;
return {
x,
y,
width,
height
}
}
}
public emit(eventName:string, args: NodeEvent, notifyChild:boolean = true)
{
let eventList = this._events.get(eventName);
if(notifyChild)
{
this.ChildNodes.filter(node => {
if(node.isMatchPoint(args.x, args.y))
{
if(!node.mouseHover)
{
node.mouseHover = true;
let event = args.clone();
event.type = "mouse:enter";
node.emit("mouse", event, false);
};
return true
}else{
if(node.mouseHover)
{
node.mouseHover = false;
let event = args.clone();
event.type = "mouse:leave";
node.emit("mouse", event);
};
return false
}
}).some(node => {
node.emit(eventName, args);
return args.prevented != false;
});
}
if(args.stoppedBubbling == false)
{
if(eventList)
{
for (const event of eventList)
{
event.apply(this, [args]);
if(args.prevented)
{
break;
}
}
};
}
};
public drawScope : Map<string, Function> = new Map();
public hasEvent(eventName: string) : boolean
{
return this.drawScope.has(eventName);
}
public handleEvent(eventName: string, onCallback?:(callback:Function) => any) : any
{
let func = this.drawScope.get(eventName);
if(func)
{
if(onCallback)
{
return onCallback(func);
}else{
return func.call(this);
}
}
}
public handleDraw(e:DrawFunction<this>)
{
this.drawScope.set("draw", e);
}
public handleDrawBefore(e:DrawFunction<this>)
{
this.drawScope.set("draw:before", e);
}
public handleDrawAfter(e:DrawFunction<this>)
{
this.drawScope.set("draw:after", e);
}
public addNode(node:Node)
{
let {
width,
height
} = this.getContextArea();
node.width = node.width || width;
node.height = node.height || height;
node.parentNode = this;
this.ChildNodes.push(node);
}
}