"use strict"; /** * @typedef {import("../../../AnP/Public/ecma/AnP.ecma.js").AnP} AnP */ /** * @constructor * @param {!AnP} anp * @param {!(Object.|Array.>)} [inputs] * @param {!anp_default_callback} [callback] * @returns {void} * @access public */ const Kanvas = function(anp, inputs, callback){ /** @type {Kanvas} */ const self = this, /** @type {Object.} */ settings = AnP.prototype.get_dictionary([{ /** @type {HTMLElement|string} */ position : "body", /** @type {boolean} */ autocreate : true, /** @type {number} */ quality : 1, /** @type {number} */ base_x : .5, /** @type {number} */ base_y : .5, /** @type {number} */ cells : 40, /** @type {boolean} */ autostart : true }, inputs]), /** @type {Object.} */ screen = {x : 0, y : 0, quality : 0}, /** @type {Object.} */ base = {x : .5, y : .5}; /** @type {boolean} */ let started = false, /** @type {HTMLCanvasElement|null} */ canvas = null, /** @type {CanvasRenderingContext2D|null} */ context = null, /** @type {number|null} */ thread = null, /** @type {number} */ quality = 1, /** @type {number} */ cell_size = 1, fps_time = 0; /** @type {Array.|Array.|null>} */ this.map = []; /** @type {Object.} */ this.screen = {x : 0, y : 0}; /** @type {number} */ this.cells = 40; this.delta = 0; this.fps = 0; /** * @returns {void} * @access private */ const constructor = () => { anp.settings.get("autostart", settings) && self.start(callback); }; /** * @param {!anp_start_callback} [callback] * @returns {boolean} * @access public */ this.start = callback => { /** * @param {!boolean} status * @returns {void} */ const end = status => AnP.prototype.execute(callback, status); if(started){ end(false); return false; }; started = true; quality = anp.settings.get("quality", settings); base.x = anp.settings.get("base_x", settings); base.y = anp.settings.get("base_y", settings); self.cells = anp.settings.get("cells", settings); if(anp.settings.get("autocreate", settings)) self.create(anp.settings.get("position", settings), () => end(true)); else end(true); return true; }; /** * @param {!number} value * @returns {number} * @access public */ this.aexis = value => value * cell_size; /** * @param {!number} value * @returns {number} * @access private */ const _ = this.aexis; /** * @returns {Array.} * @access public * @deprecated */ this.get_transform_matrix = () => { /** @type {DOMMatrix} */ const transform = context.getTransform(); return [ transform.a, transform.b, transform.c, transform.d, transform.e, transform.f ]; }; /** * @returns {void} * @access private */ const thread_method = () => { const new_time = Date.now(); if(screen.x != canvas.offsetWidth || screen.y != canvas.offsetHeight || screen.quality != quality){ screen.quality = quality; canvas.width = quality * (screen.x = canvas.offsetWidth); canvas.height = quality * (screen.y = canvas.offsetHeight); cell_size = quality * screen[screen.y < screen.x ? "y" : "x"] / self.cells; self.screen.x = quality * screen.x / cell_size; self.screen.y = quality * screen.y / cell_size; }; /** @type {Array.} */ const x = _(base.x * self.screen.x), y = _(base.y * self.screen.y); context.clearRect(0, 0, _(self.screen.x), _(self.screen.y)); context.translate(x, y); draw(self.map); context.translate(-x, -y); if(fps_time){ self.fps = 1000 / (new_time - fps_time); self.delta = 24 / self.fps; }; fps_time = new_time; }; /** * @param {!(string|HTMLElement)} position * @param {!anp_default_callback} [callback] * @returns {void} * @access public */ this.create = (position, callback) => { if(canvas) AnP.prototype.execute(callback); else new HTMLPreload(anp, position, item => { if(item){ anp.attributes.set(canvas = item.appendChild(document.createElement("canvas")), { class : ["kanvas"].concat(AnP.prototype.get_class_keys(AnP.prototype.get_value("class", settings))).join(" ").trim(), git : AnP.prototype.get_value("git", settings), link : AnP.prototype.get_value("link", settings) }); context = canvas.getContext("2d"); thread = anp.threads.add(thread_method); }; AnP.prototype.execute(callback); }); }; /** * @param {!Array.|Object.|Kanvas.prototype.ObjectBase|null>} map * @returns {void} * @access private */ const draw = map => map.forEach((item, i) => { if((AnP.prototype.is_dictionary(item) && !item.draw) || AnP.prototype.is_array(item)){ /** @type {string} */ const type = AnP.prototype.is_dictionary(item) ? item.type : item.shift(), /** @type {Kanvas.prototype.ObjectBase} */ _class = self.objects[type] || self.objects[object_synonyms[type]]; map[i] = _class ? _class(item) : null; map[i].print && console.log(map[i]); }; if(item && item.state & Kanvas.prototype.LOADED){ const base_x = _(item.x), base_y = _(item.y); context.save(); context.translate(base_x, base_y); if(item.rotation){ /** @type {number} */ const x = item.width ? _(item.width / 2) : 0, /** @type {number} */ y = item.height ? _(item.height / 2) : 0; context.translate(x, y); context.rotate(2 * Math.PI * item.rotation / 360); context.translate(-x, -y); }; item.background && (context.fillStyle = item.background); item.alpha !== null && (context.globalAlpha = item.alpha); item.draw(); item.background && context.fill(); item.childs && draw(item.childs); context.translate(-base_x, -base_y); context.restore(); }; }); /** * @constructor * @callback kanvas_builder_callback * @param {!Object.} inputs * @returns {void} */ /** * @constructor * @callback kanvas_object_callback * @param {!(Object.|Object.)} inputs * @returns {void} */ /** * * @param {!Array.} keys * @param {!(Object.|Array.>)} inputs * @param {!kanvas_builder_callback} Builder * @returns {Kanvas.prototype.ObjectBase} */ const build_shape = (keys, inputs, Builder) => { const base = new Kanvas.prototype.ObjectBase( inputs = Kanvas.prototype.to_dictionary_values(keys, inputs) ); return AnP.prototype.extends(base, true, new Builder(base, inputs)); }; /** @type {Object.} */ const object_synonyms = { rectangle : "Rectangle", image : "Image", text : "Text", image : "Image" }; /** @type {Object.} */ this.objects = { Rectangle : inputs => build_shape(["x", "y", "width", "height"], inputs, function(item, inputs){ /** @type {string} */ this.type = self.objects.Rectangle; this.type_name = "Rectangle"; this.width = AnP.prototype.get_value("width", inputs, 0); this.height = AnP.prototype.get_value("height", inputs, 0); this.state = Kanvas.prototype.LOADED; this.draw = () => context.fillRect(0, 0, _(item.width), _(item.height)); }), Image : inputs => build_shape(["source", "x", "y", "width", "height", "cut_x", "cut_y", "cut_width", "cut_height"], inputs, function(item, inputs){ const image_item = AnP.prototype.get_value(["image", "source", "src"], inputs, null), has = {any : false, all : true}, set_sizes = () => { ["x", "y", "width", "height"].forEach(key => this[key] === null && (this[key] = "xy".includes(key) ? 0 : this.image[key])); if(!has.all && has.any){ ["x", "y", "width", "height"].forEach(key => this["cut_" + key] === null && (this["cut_" + key] = "xy".includes(key) ? 0 : this.image[key])); }; }; ["x", "y", "width", "height"].forEach(key => { has.any = ( has["cut_" + key] = ( this["cut_" + key] = AnP.prototype.get_value("cut_" + key, inputs, null) ) !== null ) || has.any; has.all = has.all && has["cut_" + key]; }); this.type = self.objects.Image; this.type_name = "Image"; this.url = AnP.prototype.is_string(image_item) ? image_item : image_item ? image_item.src : null; this.image = AnP.prototype.is_object(image_item) ? image_item : null; this.width = AnP.prototype.get_value("width", inputs, null); this.height = AnP.prototype.get_value("height", inputs, null); this.state = this.image ? Kanvas.prototype.LOADED : Kanvas.prototype.UNLOADED; this.object = null; if(this.state & Kanvas.prototype.UNLOADED && !this.image && this.url){ const image_object = new Image(); item.state = Kanvas.prototype.LOADING; image_object.src = this.url; image_object.onload = () => { item.image = image_object; item.state = Kanvas.prototype.LOADED; set_sizes(); }; image_object.onerror = () => { item.state |= Kanvas.prototype.ERROR; }; }else set_sizes(); this.draw = () => context.drawImage(item.image, 0, 0, _(item.width), _(item.height)); }), Text : inputs => build_shape(["text", "x", "y"], inputs, function(item, inputs){ this.type = self.objects.Text; this.type_name = "Text"; this.text = AnP.prototype.get_value("text", inputs, ""); this.align = AnP.prototype.get_value("align", inputs, null); this.baseline = AnP.prototype.get_value("baseline", inputs, null); this.size = AnP.prototype.get_value("size", inputs, null); this.style = AnP.prototype.get_value("style", inputs, null); this.font = AnP.prototype.get_value(["font_family", "font"], inputs, null); this.direction = AnP.prototype.get_value("direction", inputs, null); this.state = Kanvas.prototype.LOADED; this.draw = () => { item.direction && (context.direction = item.direction); item.baseline && (context.textBaseline = item.baseline); (item.size || item.font || item.style) && (context.font = (item.style ? item.style + " " : "") + (quality * (item.size || 1) * cell_size) + "px " + (item.font || "sans")); item.align && (context.textAlign = item.align); context[item.background ? "fillText" : "strokeText"](item.text, 0, 0); }; }) }; constructor(); }; /** * @param {!(Array.|string)} keys * @param {!(Object.|Array.)} inputs * @returns {Object.} * @access public * @static */ Kanvas.prototype.to_dictionary_values = (keys, inputs) => ( AnP.prototype.is_array(inputs) ? AnP.prototype.get_dictionary_from_array(keys, inputs) : AnP.prototype.is_object(inputs) ? inputs || {} : {}); /** @type {number} */ Kanvas.prototype.UNLOADED = 1 << 0; /** @type {number} */ Kanvas.prototype.LOADING = 1 << 1; /** @type {number} */ Kanvas.prototype.LOADED = 1 << 2; /** @type {number} */ Kanvas.prototype.ERROR = 1 << 3; /** @type {number} */ Kanvas.prototype.UNBUILT = 1 < 4; /** * @constructor * @param {Object.} inputs * @returns {void} * @access public * @static */ Kanvas.prototype.ObjectBase = function(inputs){ /** @type {Object} */ this.type = Kanvas.prototype.ObjectBase; /** @type {string} */ this.type_name = "ObjectBase"; /** @type {boolean} */ this.full = AnP.prototype.get_value("full", inputs, false); /** @type {boolean} */ this.percentaje = AnP.prototype.get_value("percentaje", inputs, false); /** @type {number|null} */ this.rotation = AnP.prototype.get_value("rotate", inputs, null); /** @type {string|null} */ this.background = AnP.prototype.get_value(["background", "color"], inputs, null); /** @type {number|null} */ this.alpha = AnP.prototype.get_value("alpha", inputs, null); /** @type {number} */ this.state = Kanvas.prototype.UNBUILT; /** @type {Array.|Object.|Object>|null} */ this.childs = AnP.prototype.get_value("childs", inputs, null); /** @type {number} */ this.x = AnP.prototype.get_value("x", inputs, 0); /** @type {number} */ this.y = AnP.prototype.get_value("y", inputs, 0); /** @type {boolean} */ this.print = AnP.prototype.get_value("print", inputs, false); /** * @param {number} degrees * @returns {void} * @access public */ this.rotate = degrees => this.rotation = (this.rotation + degrees) % 360; /** * @returns {void} * @access public */ this.draw = () => {}; };