commit a187e04b80aa693bcd58840987013206e6fe4091 Author: Abdussamed Date: Sun Jun 11 16:01:50 2023 +0300 Yedek diff --git a/Compositor/Canvas.ts b/Compositor/Canvas.ts new file mode 100644 index 0000000..df7827f --- /dev/null +++ b/Compositor/Canvas.ts @@ -0,0 +1,148 @@ +import Node from "./Node"; +import NodeEvent from "./NodeEvent"; +export default class Canvas +{ + public context : CanvasRenderingContext2D; + public canvas : HTMLCanvasElement; + public width : number; + public height : number; + + public rootNode = new Node("Root"); + public nodes: [Node,number][] = []; + + public pool : DrawPool = new DrawPool(); + + public constructor() + { + this.canvas = document.createElement("canvas"); + this.context = this.canvas.getContext("2d") as CanvasRenderingContext2D; + this.installMouseManager(); + this.installKeyboardManager(); + } + public init() + { + this.canvas.style.imageRendering = "pixelated"; + window.document.body.appendChild(this.canvas); + this.recalculateSize(); + window.addEventListener("resize",() => this.recalculateSize()); + this.pool.trigger = () => this.drawContext(); + } + public installMouseManager() + { + this.canvas.addEventListener("mousedown",(event) => { + let mouseEvent = new NodeEvent(); + mouseEvent.x = event.offsetX; + mouseEvent.y = event.offsetY; + mouseEvent.type = "mouse:down"; + this.rootNode.emit("mouse", mouseEvent); + }); + this.canvas.addEventListener("mousemove",(event) => { + let mouseEvent = new NodeEvent(); + mouseEvent.x = event.offsetX; + mouseEvent.y = event.offsetY; + mouseEvent.data = { + deltaX: event.movementX, + deltaY: event.movementY + }; + mouseEvent.type = "mouse:move"; + this.rootNode.emit("mouse", mouseEvent); + }); + this.canvas.addEventListener("mouseup",(event) => { + let mouseEvent = new NodeEvent(); + mouseEvent.x = event.offsetX; + mouseEvent.y = event.offsetY; + mouseEvent.type = "mouse:up"; + this.rootNode.emit("mouse", mouseEvent); + }); + this.canvas.addEventListener("click",(event) => { + let mouseEvent = new NodeEvent(); + mouseEvent.x = event.offsetX; + mouseEvent.y = event.offsetY; + mouseEvent.type = "mouse:click"; + this.rootNode.emit("mouse", mouseEvent); + }); + this.canvas.addEventListener("wheel",(event) => { + let mouseEvent = new NodeEvent(); + mouseEvent.x = event.offsetX; + mouseEvent.y = event.offsetY; + mouseEvent.data = { + xModulus: event.deltaX == 0 ? 0 : event.deltaX < 0 ? -1 : +1, + yModulus: event.deltaY == 0 ? 0 : event.deltaY < 0 ? -1 : +1, + xScroll: event.deltaX, + yScroll: event.deltaY + }; + mouseEvent.type = "mouse:wheel"; + this.rootNode.emit("mouse", mouseEvent); + }); + } + public installKeyboardManager() + { + + } + public recalculateSize() + { + let { + width, + height + } = document.body.getClientRects()[0]; + this.canvas.setAttribute("width", width + "px"); + this.canvas.setAttribute("height", height + "px"); + this.canvas.style.width = width + "px"; + this.canvas.style.height = height + "px"; + this.width = width; + this.height = height + this.pool.notify(); + } + public addNode(node:Node, priority : number= 0) + { + this.nodes.push([node,priority]); + node.on('draw',() => { + this.pool.notify(); + }); + this.pool.notify(); + } + public drawContext() + { + this.rootNode.width = this.width; + this.rootNode.height = this.height; + this.rootNode.ChildNodes = this.nodes.sort(([,n], [,k]) => n - k).map(e => e[0]); + this.context.clearRect(0, 0, this.width, this.height); + this.rootNode.context.resize(this.rootNode.width, this.rootNode.height); + this.rootNode.draw().writeTo( + this.context, + 0, + 0, + this.rootNode.width, + this.rootNode.height, + 0, + 0, + this.rootNode.width, + this.rootNode.height + ); + } +}; + +class DrawPool +{ + public busy : boolean = true; + public trigger : Function; + public inf : number = -1; + public addPool(e:Function) + { + if(!this.busy) + { + this.notify() + }else this.trigger = e; + } + public notify() + { + this.busy = true; + this.inf = requestAnimationFrame(() => { + if(this.trigger) + { + this.trigger(); + } + this.busy = false; + }); + } +} \ No newline at end of file diff --git a/Compositor/CanvasBuffer.ts b/Compositor/CanvasBuffer.ts new file mode 100644 index 0000000..e976bfe --- /dev/null +++ b/Compositor/CanvasBuffer.ts @@ -0,0 +1,168 @@ +export default class CanvasBuffer +{ + public offscreen! : OffscreenCanvas; + public context! : OffscreenCanvasRenderingContext2D; + public width: number = 0; + public height: number = 0; + constructor(width?:number, height?:number){ + this.offscreen = new OffscreenCanvas(width || 0, height || 0); + this.context = this.offscreen.getContext("2d") as OffscreenCanvasRenderingContext2D; + this.width = width || 0; + this.height = height || 0; + } + public resize(width: number, height: number) + { + if(this.width != width) + { + this.offscreen.width = width; + this.width = width || 0; + } + if(this.height != height) + { + this.offscreen.height = height; + this.height = height || 0; + } + } + public clear() + { + this.context.clearRect(0, 0, this.offscreen.width, this.offscreen.height); + } + public writeFrom( + canvas: OffscreenCanvas | OffscreenCanvasRenderingContext2D | HTMLCanvasElement | CanvasRenderingContext2D | CanvasBuffer, + x?:number, + y?:number, + width?:number, + height?:number, + dx?:number, + dy?:number, + dwidth?:number, + dheight?:number + ) + { + let args = [ + x, + y, + width, + height, + dx, + dy, + dwidth, + dheight + ]; + if( + canvas instanceof OffscreenCanvas || + canvas instanceof HTMLCanvasElement + ) + { + _draw_content_to_canvas( + this.context, + canvas, + ...args + ); + } + if( + canvas instanceof OffscreenCanvasRenderingContext2D || + canvas instanceof CanvasRenderingContext2D + ) + { + _draw_content_to_canvas( + this.context, + canvas.canvas, + ...args + ); + } + if( + canvas instanceof CanvasBuffer + ) + { + _draw_content_to_canvas( + this.context, + canvas.offscreen, + ...args + ); + } + } + public writeTo( + canvas: OffscreenCanvas | OffscreenCanvasRenderingContext2D | HTMLCanvasElement | CanvasRenderingContext2D | CanvasBuffer, + x?:number, + y?:number, + width?:number, + height?:number, + dx?:number, + dy?:number, + dwidth?:number, + dheight?:number + ) + { + let args = [ + x, + y, + width, + height, + dx, + dy, + dwidth, + dheight + ]; + if( + canvas instanceof OffscreenCanvas || + canvas instanceof HTMLCanvasElement + ) + { + let content = canvas.getContext("2d") as OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D; + + _draw_content_to_canvas( + content, + this.offscreen, + ...args + ); + } + if( + canvas instanceof OffscreenCanvasRenderingContext2D || + canvas instanceof CanvasRenderingContext2D + ) + { + _draw_content_to_canvas( + canvas, + this.offscreen, + ...args + ); + } + if( + canvas instanceof CanvasBuffer + ) + { + _draw_content_to_canvas( + canvas.context, + this.offscreen, + ...args + ); + } + } +} + +function _draw_content_to_canvas( + context : OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D, + canvas : OffscreenCanvas | HTMLCanvasElement, + x?:number, + y?:number, + width?:number, + height?:number, + dx?:number, + dy?:number, + dwidth?:number, + dheight?:number +) +{ + context.drawImage( + canvas, + x || 0, + y || 0, + width || canvas.width, + height || canvas.height, + dx || 0, + dy || 0, + dwidth || canvas.width, + dheight || canvas.height + ) +} \ No newline at end of file diff --git a/Compositor/Node.ts b/Compositor/Node.ts new file mode 100644 index 0000000..1b677c5 --- /dev/null +++ b/Compositor/Node.ts @@ -0,0 +1,409 @@ + +type DrawArea = {width:number,height:number}; +type DrawFunction = (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 + { + 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.context.resize(width, height); + + this.buffered = true; + + for (const node of this.ChildNodes) + { + this.DrawChild( + this.context, + node, + width, + 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; + x += this.padding.left; + y += this.padding.top; + + 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, + frame.offscreen.width * node.scale, + frame.offscreen.height * 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) + { + 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 = 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.drawScope.set("draw", e); + } + public handleDrawBefore(e:DrawFunction) + { + this.drawScope.set("draw:before", e); + } + public handleDrawAfter(e:DrawFunction) + { + 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); + } +} \ No newline at end of file diff --git a/Compositor/NodeEvent.ts b/Compositor/NodeEvent.ts new file mode 100644 index 0000000..4c1d01b --- /dev/null +++ b/Compositor/NodeEvent.ts @@ -0,0 +1,24 @@ +export default class NodeEvent +{ + public type = ""; + public prevented = false; + public stoppedBubbling = false; + public data : any = {}; + public keyCode : number; + public key : string; + public x : number; + public y : number; + public clone() + { + let u = new NodeEvent(); + u.type = this.type; + u.prevented = this.prevented; + u.stoppedBubbling = this.stoppedBubbling; + u.data = this.data; + u.keyCode = this.keyCode; + u.key = this.key; + u.x = this.x; + u.y = this.y; + return u; + } +} \ No newline at end of file diff --git a/index.css b/index.css new file mode 100644 index 0000000..a242433 --- /dev/null +++ b/index.css @@ -0,0 +1,5 @@ +html,body{ + margin: 0; + height: 100%; + overflow: hidden; +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..08d97f0 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Indexed + + + + + + \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..901c26f --- /dev/null +++ b/index.js @@ -0,0 +1,1161 @@ +// modules are defined as an array +// [ module function, map of requires ] +// +// map of requires is short require name -> numeric require +// +// anything defined in a previous bundle is accessed via the +// orig method which is the require for previous bundles + +(function (modules, entry, mainEntry, parcelRequireName, globalName) { + /* eslint-disable no-undef */ + var globalObject = + typeof globalThis !== 'undefined' + ? globalThis + : typeof self !== 'undefined' + ? self + : typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' + ? global + : {}; + /* eslint-enable no-undef */ + + // Save the require from previous bundle to this closure if any + var previousRequire = + typeof globalObject[parcelRequireName] === 'function' && + globalObject[parcelRequireName]; + + var cache = previousRequire.cache || {}; + // Do not use `require` to prevent Webpack from trying to bundle this call + var nodeRequire = + typeof module !== 'undefined' && + typeof module.require === 'function' && + module.require.bind(module); + + function newRequire(name, jumped) { + if (!cache[name]) { + if (!modules[name]) { + // if we cannot find the module within our internal map or + // cache jump to the current global require ie. the last bundle + // that was added to the page. + var currentRequire = + typeof globalObject[parcelRequireName] === 'function' && + globalObject[parcelRequireName]; + if (!jumped && currentRequire) { + return currentRequire(name, true); + } + + // If there are other bundles on this page the require from the + // previous one is saved to 'previousRequire'. Repeat this as + // many times as there are bundles until the module is found or + // we exhaust the require chain. + if (previousRequire) { + return previousRequire(name, true); + } + + // Try the node require function if it exists. + if (nodeRequire && typeof name === 'string') { + return nodeRequire(name); + } + + var err = new Error("Cannot find module '" + name + "'"); + err.code = 'MODULE_NOT_FOUND'; + throw err; + } + + localRequire.resolve = resolve; + localRequire.cache = {}; + + var module = (cache[name] = new newRequire.Module(name)); + + modules[name][0].call( + module.exports, + localRequire, + module, + module.exports, + this + ); + } + + return cache[name].exports; + + function localRequire(x) { + var res = localRequire.resolve(x); + return res === false ? {} : newRequire(res); + } + + function resolve(x) { + var id = modules[name][1][x]; + return id != null ? id : x; + } + } + + function Module(moduleName) { + this.id = moduleName; + this.bundle = newRequire; + this.exports = {}; + } + + newRequire.isParcelRequire = true; + newRequire.Module = Module; + newRequire.modules = modules; + newRequire.cache = cache; + newRequire.parent = previousRequire; + newRequire.register = function (id, exports) { + modules[id] = [ + function (require, module) { + module.exports = exports; + }, + {}, + ]; + }; + + Object.defineProperty(newRequire, 'root', { + get: function () { + return globalObject[parcelRequireName]; + }, + }); + + globalObject[parcelRequireName] = newRequire; + + for (var i = 0; i < entry.length; i++) { + newRequire(entry[i]); + } + + if (mainEntry) { + // Expose entry point to Node, AMD or browser globals + // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js + var mainExports = newRequire(mainEntry); + + // CommonJS + if (typeof exports === 'object' && typeof module !== 'undefined') { + module.exports = mainExports; + + // RequireJS + } else if (typeof define === 'function' && define.amd) { + define(function () { + return mainExports; + }); + + //