456 lines
15 KiB
JavaScript
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 = () => {};
|
|
|
|
}; |