Kanvas/Public/ecma/version/Kanvas.AnP.ecma.js

456 lines
15 KiB
JavaScript

"use strict";
/**
* @typedef {import("../../../AnP/Public/ecma/AnP.ecma.js").AnP} AnP
*/
/**
* @constructor
* @param {!AnP} anp
* @param {!(Object.<string, any|null>|Array.<Object.<string, any|null>>)} [inputs]
* @param {!anp_default_callback} [callback]
* @returns {void}
* @access public
*/
const Kanvas = function(anp, inputs, callback){
/** @type {Kanvas} */
const self = this,
/** @type {Object.<string, any|null>} */
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.<string, number>} */
screen = {x : 0, y : 0, quality : 0},
/** @type {Object.<string, number>} */
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.<Kanvas.prototype.ObjectBase|Object.<string, any|null>|Array.<any|null>|null>} */
this.map = [];
/** @type {Object.<string, number>} */
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.<number>}
* @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.<number>} */
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.<Array.<any|null>|Object.<string, any|null>|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.<string, any|null>} inputs
* @returns {void}
*/
/**
* @constructor
* @callback kanvas_object_callback
* @param {!(Object.<string, any|null>|Object.<any|null>)} inputs
* @returns {void}
*/
/**
*
* @param {!Array.<string>} keys
* @param {!(Object.<string, any|null>|Array.<any|null>>)} 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.<string, string>} */
const object_synonyms = {
rectangle : "Rectangle",
image : "Image",
text : "Text",
image : "Image"
};
/** @type {Object.<string, kanvas_object_callback>} */
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>|string)} keys
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @returns {Object.<string, any|null>}
* @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.<string, any|null>} 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.<Array.<any>|Object.<string, any|null>|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 = () => {};
};