Kanvas/Public/ecma/Kanvas.ecma.js

2190 lines
73 KiB
JavaScript

"use strict";
/**
* @callback kanvas_event_callback
* @param {!number} i
* @param {...any} [parameters]
* @returns {Array.<any|null>}
*/
/**
* @callback kanvas_thread_callback
* @param {!Kanvas.Thread} thread
*/
/**
* @callback kanvas_execute_callback
* @param {...any} [parameters]
* @returns {any|null}
*/
/**
* @callback kanvas_preload_callback
* @param {?HTMLElement} item
* @param {!boolean} asynchronous
* @returns {void}
*/
/**
* @callback kanvas_default_callback
* @returns {void}
*/
/**
* @class
* @constructor
* @param {!(Object.<string, any|null>|Array.<any|null>)} [inputs = {}]
* @returns {void}
* @access public
*/
export const Kanvas = (function(){
/**
* @constructs Kanvas
* @param {!(Object.<string, any|null>|Array.<any|null>)} [inputs = {}]
* @returns {void}
* @access private
*/
const Kanvas = function(inputs = {}){
/** @type {Kanvas} */
const self = this,
/** @type {Object.<string, any|null>} */
settings = Kanvas.get_dictionary(inputs, Kanvas.OVERWRITE),
/** @type {Array.<any>} */
threads = [],
/** @type {Kanvas.Cache} */
cache = new Kanvas.Cache(),
/** @type {Object.<string, Image>} */
images_cache = {},
/** @type {Array.<number>} */
thread_times = [];
/** @type {Kanvas.Thread|null} */
let screen_thread = null,
/** @type {Kanvas.Thread|null} */
autodraw_thread = null,
/** @type {boolean} */
clicking = false,
/** @type {number} */
last_time = 0,
/** @type {number} */
thread_times_summatory = 0,
/** @type {Kanvas.Thread|null} */
delta_thread = null;
/** @type {number} */
this.threads_mode = Kanvas.THREADS_MODE_ANIMATION;
/** @type {number} */
this.frames_per_second = 60;
/** @type {number} */
this.preload_timeout = 2000;
/** @type {number} */
this.delta = 0;
/** @type {number} */
this.cells = 40;
/** @type {number} */
this.quality = 1;
/** @type {number} */
this.start_x = .5;
/** @type {number} */
this.start_y = .5;
/** @type {number} */
this.cells_x = 0;
/** @type {number} */
this.cells_y = 0;
/** @type {number} */
this.cell_size = 0;
/** @type {number} */
this.x = 0;
/** @type {number} */
this.y = 0;
/** @type {number} */
this.default_font_family = "monospace";
/** @type {number} */
this.frames_per_second = 24;
/** @type {number} */
this.real_frames_per_second = 0;
/** @type {number} */
this.thread_times_samples = 3;
/** @type {number} */
this.difference = 0;
/** @type {HTMLElement|null} */
this.item_self = null;
/** @type {HTMLCanvasElement|null} */
this.canvas = null;
/** @type {CanvasRenderingContext2D|null} */
this.context = null;
/** @type {Kanvas.Event} */
this.on_ready = new Kanvas.Event(Kanvas.ONCE);
/** @type {Kanvas.Event} */
this.on_change_screen = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_mouse_down = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_mouse_up = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_click = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_mouse_over = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_mouse_out = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_mouse_move = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_key_down = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_key_up = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_key_press = new Kanvas.Event();
/** @type {Array.<any|null>} */
this.map = [];
/**
* @returns {void}
* @access private
*/
const constructor = () => {
/** @type {string|HTMLElement|null} */
const position = self.settings("position"),
on_ready_method = self.settings("on_ready");
self.threads_mode = self.settings("threads_mode", null, self.threads_mode);
self.start_x = self.settings("start_x", null, self.start_x);
self.start_y = self.settings("start_y", null, self.start_y);
self.preload_timeout = self.settings(["preload_timeout", "timeout"], null, self.preload_timeout);
self.default_font_family = self.settings("default_font_family", null, self.default_font_family);
self.quality = self.settings(["quality", "q"], null, self.quality);
self.cells = self.settings("cells", null, self.cells);
self.thread_times_samples = self.settings("thread_times_samples", null, self.thread_times_samples);
self.frames_per_second = self.settings(["frames_per_second", "fps"], null, self.frames_per_second);
on_ready_method && self.on_ready.add(on_ready_method);
execute_threads_by_mode();
position && self.set(position);
};
/**
* @returns {number}
* @access private
*/
const thread_timer = () => self.frames_per_second && 1000 / self.frames_per_second;
/**
* @returns {void}
* @access private
*/
const execute_threads_by_mode = () => {
switch(self.threads_mode){
case Kanvas.THREADS_MODE_INTERVAL:
/** @type {Object.<string, number>} */
const cache = {
frames_per_second : self.frames_per_second,
mode : self.threads_mode
},
/** @type {number} */
interval = setInterval(() => {
execute_threads();
if(
cache.frames_per_seconds != self.frames_per_second ||
cache.mode != self.threads_mode
){
clearInterval(interval);
execute_threads_by_mode();
};
}, thread_timer());
break;
case Kanvas.THREADS_MODE_TIMEOUT:
setTimeout(() => {
execute_threads();
execute_threads_by_mode();
}, thread_timer());
break;
case Kanvas.THREADS_MODE_ANIMATION:
default:
/** @type {number} */
const time = Date.now(),
/** @type {number} */
timer = thread_timer();
requestAnimationFrame(() => {
execute_threads();
// execute_threads_by_mode();
setTimeout(() => {
execute_threads_by_mode();
}, timer - (Date.now() - time));
});
break;
};
};
/**
* @returns {void}
* @access private
*/
const execute_threads = () => {
threads.forEach(thread => {
thread && thread.execute();
});
};
/**
* @param {!(string|Array.<string>)} keys
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @param {?any} [_default = null]
* @param {!number} [options = 0]
* @returns {any|null}
* @access public
*/
this.settings = (keys, inputs = null, _default = null, options = 0) => {
return Kanvas.get_value(keys, [inputs, settings], _default, options);
};
/**
* @param {!kanvas_thread_callback} callback
* @param {?(Object.<string, any|null>|Array.<any|null>)} inputs
* @returns {Kanvas.Thread}
* @access public
*/
this.thread_add = (callback, inputs = null) => {
/** @type {Kanvas.Thread} */
const thread = new Kanvas.Thread(self, callback, inputs),
/** @type {number} */
l = threads.length;
for(thread.i = 0; thread.i < l; thread.i ++)
if(!threads[thread.i])
break;
threads[thread.i] = thread;
return thread;
};
/**
* @param {!Kanvas.Thread} thread
* @returns {void}
* @access public
*/
this.thread_remove = thread => {
thread instanceof Kanvas.Thread && threads[thread.i] && (threads[thread.i] = null);
};
/**
* @param {!(string|HTMLElement)} selector
* @param {!kanvas_preload_callback} callback
* @param {!number} timeout
* @returns {Array.<HTMLElement|null, boolean>}
* @access public
*/
this.preload_html_item = (selector, callback, timeout = 0) => {
/** @type {kanvas_preload_callback} */
const end = (item, asynchronous) => {
Kanvas.execute(callback, item, asynchronous);
return [item, asynchronous];
};
if(Kanvas.is_html_item(selector))
return end(selector, false);
if(Kanvas.is_string(selector)){
/** @type {HTMLElement|null} */
let item;
try{
if(item = document.querySelector(selector))
return end(item, false);
}catch(exception){
return end(null, false);
};
timeout || (timeout = self.preload_timeout);
/** @type {number} */
const time = Date.now(),
/** @type {Kanvas.Thread} */
thread = self.thread_add(() => {
if(item = document.querySelector(selector)){
self.thread_remove(thread);
end(item, true);
}else if(Date.now() - time > timeout){
self.thread_remove(thread);
end(null, true);
};
}, {
/** @type {boolean} */
start_now : true,
/** @type {boolean} */
bucle : true
});
}else
return end(null, false);
};
const check_map_events = (event, device, events = null) => {
self.map.forEach(item => {
item && item.status && !item.ignore_events && item["check_" + device](event, events);
});
};
/**
* @param {!(string|HTMLElement)} position
* @param {?kanvas_default_callback} [callback = null]
* @returns {void}
* @access public
*/
this.set = (position, callback = null) => {
self.preload_html_item(position, (item, asynchronous) => {
if(item){
if(item.tagName.toLowerCase() == "canvas"){
self.canvas = item;
(self.item_self = item.parentNode).classList.contains("kanvas") || self.item_self.classList.add("kanvas");
}else if(item.classList.contains("kanvas"))
self.item_self = item;
else{
(self.item_self = item.appendChild(document.createElement("div"))).setAttribute("class", "kanvas");
self.canvas = self.item_self.appendChild(document.createElement("canvas"));
};
self.item_self.setAttribute("data-cells", self.cells);
self.item_self.setAttribute("data-quality", self.quality);
self.context = self.canvas.getContext("2d");
self.canvas.addEventListener("mousemove", event => {
check_map_events(event, "mouse");
self.on_mouse_move.execute(event);
});
self.canvas.addEventListener("mousedown", event => {
check_map_events(event, "click", Kanvas.MOUSE_DOWN);
clicking || (clicking = true);
self.on_mouse_down.execute(event);
});
self.canvas.addEventListener("mouseup", event => {
check_map_events(event, "click", Kanvas.MOUSE_UP | (clicking ? Kanvas.MOUSE_CLICK : 0));
self.on_mouse_up.execute(event);
if(clicking){
clicking = false;
self.on_click.execute(event);
};
});
self.canvas.addEventListener("mouseout", event => {
clicking && (clicking = false);
self.on_mouse_out.execute(event);
});
self.canvas.addEventListener("mouseover", event => {
self.on_mouse_over.execute(event);
});
screen_thread = self.thread_add(screen_thread_method, {
bucle : true,
start_now : true
});
delta_thread = self.thread_add(screen_delta_method, {
bucle : true,
start_now : true
});
autodraw_thread = self.thread_add(draw_thread_method, {
bucle : true,
start_now : false
});
if(asynchronous){
self.on_ready.mode |= Kanvas.AUTOEXECUTE;
self.on_ready.execute();
}else
setTimeout(() => {
self.on_ready.mode |= Kanvas.AUTOEXECUTE;
self.on_ready.execute();
}, 0);
};
Kanvas.execute(callback);
});
};
const screen_delta_method = () => {
try{
/** @type {number} */
const time = performance.now();
if(last_time){
/** @type {number} */
const difference = last_time ? time - last_time : 0;
self.difference = difference;
self.delta = difference / 100;
thread_times.push(difference);
thread_times_summatory += difference;
if(thread_times.length){
while(thread_times.length > self.thread_times_samples)
thread_times_summatory -= thread_times.shift();
/** @type {number} */
const average = thread_times_summatory / thread_times.length;
average && (self.real_frames_per_second = 1000 / average);
};
};
last_time = time;
}catch(exception){
console.error(exception);
};
};
const draw_thread_method = () => {
self.context.clearRect(0, 0, self.width, self.height);
self.draw(self.context);
};
/**
* @returns {void}
* @access private
*/
const screen_thread_method = () => {
if(cache.x != self.canvas.offsetWidth || cache.y != self.canvas.offsetHeight || cache.quality != self.quality){
cache.x = self.canvas.offsetWidth;
cache.y = self.canvas.offsetHeight;
cache.quality = self.quality;
self.cell_size = (cache.x < cache.y ? cache.x : cache.y) / self.cells;
self.cells_x = cache.x / self.cell_size;
self.cells_y = cache.y / self.cell_size;
self.x = cache.x * this.start_x;
self.y = cache.y * this.start_y;
self.item_self.style.fontSize = self.cell_size + "px";
self.canvas.setAttribute("width", self.width = cache.x * self.quality);
self.canvas.setAttribute("height", self.height = cache.y * self.quality);
self.on_change_screen.execute();
};
};
/**
* @param {!number} value
* @returns {number}
* @access public
*/
this._ = value => {
return value * self.quality * self.cell_size;
};
/**
* @param {!Array.<Kanvas.ObjectBase|Array.<any|null>|Object.<string, any|null>>} level
* @param {!string} name
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @param {!number} i
* @param {?Kanvas.ObjectBase} parent
* @returns {void}
* @access private
*/
const build_shape = (level, name, inputs, i, parent) => {
if(Kanvas.SHAPES[name]){
try{
(level[i] = new Kanvas.SHAPES[name](self, parent, inputs, i)).check_full_done();
}catch(exception){
console.error(exception);
};
}else
level[i] = null;
return level[i];
};
/**
* @param {!CanvasRenderingContext2D} context
* @param {?Array.<Kanvas.ObjectBase|Array.<any|null>|Object.<string, any|null>>} level
* @param {?Kanvas.ObjectBase} parent
* @returns {void}
* @access public
*/
this.draw = (context, level = null, parent = null) => {
(level || (level = self.map)).forEach((item, i) => {
if(Kanvas.is_array(item)){
if(Kanvas.is_string(item[0]))
item = build_shape(level, item[0], item, i, parent);
else
self.draw(context, item);
}else if(Kanvas.is_dictionary(item))
item = build_shape(level, item.type, item, i, parent);
item && item.draw_level(context);
});
};
/**
* @param {!(string|Image)} source
* @param {!kanvas_load_image_callback} callback
* @returns {void}
* @access public
*/
this.preload_image = (source, callback) => {
if(Kanvas.is_image(source))
callback(source, ok);
else if(images_cache[source])
callback(...images_cache[source]);
else
Kanvas.load_image(source, (image, ok) => {
callback(...(images_cache[source] = [image, ok]));
});
};
constructor();
};
/**
* @class
* @constructor
* @returns {void}
* @access public
* @static
*/
Kanvas.Cache = function(){
/** @type {number} */
this.x = 0;
/** @type {number} */
this.y = 0;
/** @type {number} */
this.quality = 0;
};
/**
* @class
* @constructor
* @param {!number} [mode = 0]
* @returns {void}
* @access public
* @static
*/
Kanvas.Event = function(mode = 0){
/** @type {Event} */
const self = this,
/** @type {Array.<kanvas_event_callback|null>} */
events = [];
this.mode = mode;
/**
* @param {...any} [parameters]
* @returns {Array.<any|null>}
* @access public
*/
this.execute = (...parameters) => events.map((callback, i) => {
if(callback){
/** @type {any|null} */
const response = callback(i, ...parameters);
self.mode & Kanvas.ONCE && (events[i] = null);
return response;
};
return null;
});
/**
* @param {!kanvas_event_callback} callback
* @returns {number|null}
* @access public
*/
this.add = callback => {
/** @type {number|null} */
let i = null;
if(Kanvas.is_function(callback)){
if(self.mode & Kanvas.AUTOEXECUTE)
callback();
else{
/** @type {number} */
const l = events.length;
for(i = 0; i < l; i ++)
if(!events[i])
break;
events[i] = callback;
};
};
return i;
};
/**
* @param {!number} i
* @returns {void}
* @access public
*/
this.remove = i => {
events[i] && (events[i] = null);
};
};
/**
* @class
* @constructor
* @param {!Kanvas} kanvas
* @param {!kanvas_thread_callback} callback
* @param {!(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
*/
Kanvas.Thread = function(kanvas, callback, inputs = null){
/** @type {Kanvas.Thread} */
const self = this;
/** @type {number} */
let rest_time = 0;
/** @type {kanvas_thread_callback} */
this.callback = callback;
/** @type {boolean} */
this.autostart = kanvas.settings(["thread_autostart", "autostart"], inputs, true);
/** @type {number} */
this.last_date = kanvas.settings(["thread_start_now", "start_now"], inputs, true) ? 0 : Date.now();
/** @type {boolean} */
this.bucle = kanvas.settings(["thread_bucle", "bucle"], inputs, false);
/** @type {number} */
this.timer = kanvas.settings(["thread_timer", "timer"], inputs, 0);
/** @type {number} */
this.iterations = 0;
/** @type {number} */
this.error = Kanvas.is_function(this.callback) ? 0 : 1 << 1;
/** @type {boolean} */
this.autoremove = kanvas.settings(["thread_autoremove", "autoremove"], inputs, true);
/** @type {number} */
this.i = 0;
/** @type {boolean} */
this.allow_rest = kanvas.settings(["thread_allow_rest", "allow_rest"], inputs, true);
Kanvas.is_number(this.timer = kanvas.settings(["thread_timer", "timer"], inputs)) || (
(this.timer = kanvas.settings(["thread_frames_per_second", "thread_fps", "frames_per_second", "fps"], inputs, 60) || 0) &&
(this.timer = 1000 / this.timer)
);
/**
* @returns {void}
* @access public
*/
this.execute = () => {
if(!(self.error >> 1) && (self.bucle || !self.iterations)){
/** @type {number} */
const date = Date.now();
if(date + rest_time - self.last_date > self.timer){
rest_time = self.allow_rest && self.timer ? date + rest_time - self.last_date % self.timer : 0;
self.last_date = date;
try{
Kanvas.execute(self.callback);
}catch(exception){
self.error |= 1 << 0;
};
self.iterations ++;
};
};
};
};
/**
* @class
* @access public
* @static
*/
Kanvas.ObjectBase = class{
/**
* @param {!(string|Array.<string>)} keys
* @param {?any} [_default = null]
* @returns {any|null}
* @access protected
*/
_get(keys, _default = null){
return Kanvas.get_value(keys, this.inputs, _default);
};
/**
* @returns {void}
* @access public
*/
check_full_done(){
if(this.childs && this.childs.length && this.childs.some(child => Kanvas.is_dictionary(child)))
setTimeout(() => {
this.check_full_done();
}, 0);
else{
if(this.done && !this.childs.some(child => child && !child.full_done)){
this.full_done = true;
this.on_full_done.execute();
}else if(this.full_done)
this.full_done = false;
};
};
/**
* @returns {void}
* @access private
*/
#set_done(){
this.done = true;
this.check_full_done();
this.parent && this.parent.check_full_done();
};
/**
* @param {!number} status
* @returns {void}
* @access public
*/
set_status(status){
/** @type {boolean} */
const changed = this.status != status;
if((this.status = status) & Kanvas.LOADED){
if(!(this.on_load.mode & Kanvas.AUTOEXECUTE)){
this.on_load.mode |= Kanvas.AUTOEXECUTE;
this.on_load.execute(this);
this.#set_done();
};
}else if(status & Kanvas.ERROR){
this.on_error.execute(this);
this.#set_done();
};
changed && this.on_status_change.execute(this);
};
/**
* @param {?any} value
* @returns {string}
* @access public
* @static
*/
static get_inputs_type(value){
return (
Kanvas.is_string(value) ? "string" :
Kanvas.is_number(value) ? "number" :
Kanvas.is_array(value) ? "array" :
"unknown");
};
/**
* @param {!(Array.<any|null>|Object.<string, any|null>)} inputs
* @param {!Object.<string, Array.<string>>} array_map
* @returns {Object.<string, any|null>}
* @access public
* @static
*/
static build_inputs(inputs, array_map){
if(Kanvas.is_array(inputs)){
/** @type {Object.<string, number>} */
const map_i = Object.keys(array_map).reduce((dictionary, key) => {
dictionary[key] = 0;
return dictionary;
}, {});
inputs = inputs.slice(1).reduce((dictionary, value) => {
/** @type {string} */
const type = Kanvas.ObjectBase.get_inputs_type(value);
map_i[type] !== undefined &&
array_map[type] &&
map_i[type] < array_map[type].length &&
(dictionary[array_map[type][map_i[type] ++]] = value);
return dictionary;
}, {});
}else if(!Kanvas.is_dictionary(inputs))
inputs = {};
return inputs;
};
/**
* @constructor
* @param {!Kanvas} kanvas
* @param {!Kanvas.ObjectBase} parent
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @param {?number} [i = null]
* @param {!Object.<string, string>} [array_map = {}]
* @access public
*/
constructor(kanvas, parent, inputs, i = null, array_map = {}){
/** @type {Object.<string, any|null>|Array.<any|null>} */
this.inputs = Kanvas.ObjectBase.build_inputs(inputs, array_map);
/** @type {Kanvas} */
this.kanvas = kanvas;
/** @type {Kanvas.ObjectBase} */
this.parent = parent;
/** @type {Object} */
this.type = Kanvas.ObjectBase;
/** @type {string} */
this.type_name = "ObjectBase";
/** @type {number} */
this.x = this._get("x", 0);
/** @type {number} */
this.y = this._get("y", 0);
/** @type {number} */
this.width = this._get("width", 0);
/** @type {number} */
this.height = this._get("height", 0);
/** @type {number|null} */
this.rotation = this._get(["rotate", "rotation"], 0);
/** @type {string|null} */
this.background = this._get(["background", "color"], null);
/** @type {number|null} */
this.alpha = this._get("alpha", null);
/** @type {Array.<Kanvas.ObjectBase|Array.<any|null>|Object.<string, any|null>} */
this.childs = this._get("childs", []);
/** @type {string} */
this.border_color = this._get("border_color", null);
/** @type {number} */
this.border_size = this._get(["border_size", "border_weight"], null);
/** @type {Array.<Kanvas.Shadow>} */
this.shadows = Kanvas.get_array(this._get(["shadow", "shadows"], [])).map(shadow => new Kanvas.Shadow(...shadow));
/** @type {Path2D|null} */
this.path = null;
/** @type {DOMMatrix|null} */
this.matrix = null;
/** @type {number} */
this.status = Kanvas.UNBUILT;
/** @type {number|null} */
this.i = i;
/** @type {number} */
this.ignore_mouse_events = this._get("ignore_mouse_events", false);
/** @type {boolean} */
this.draw_childs = this._get("draw_childs", true);
/** @type {boolean} */
this.registered = this._get("registered", false);
/** @type {boolean} */
this.done = false;
/** @type {boolean} */
this.full_done = false;
/** @type {boolean} */
this.autoset_full_done = this._get("autoset_full_done", true);
/** @type {Kanvas.Event} */
this.on_load = new Kanvas.Event(Kanvas.ONCE);
/** @type {Kanvas.Event} */
this.on_full_done = new Kanvas.Event(Kanvas.ONCE);
/** @type {Kanvas.Event} */
this.on_error = new Kanvas.Event(Kanvas.ONCE);
/** @type {Kanvas.Event} */
this.on_status_change = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_mouse_over = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_mouse_out = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_mouse_move = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_click = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_mouse_down = new Kanvas.Event();
/** @type {Kanvas.Event} */
this.on_mouse_up = new Kanvas.Event();
[
"load", "error", "status_change", "mouse_down", "mouse_up", "click",
"mouse_over", "mouse_out", "mouse_move", "full_done"
].forEach(key => {
/** @type {kanvas_event_callback} */
const event = this._get("on_" + key);
Kanvas.is_function(event) && this["on_" + key].add(event);
});
};
/**
* @param {!DOMMatrix} matrix
* @returns {Array.<number, number, number, number, number, number>}
* @access public
* @static
*/
static transform_array(matrix){
return [matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f];
};
/**
* @param {!EventMouse} event
* @returns {void}
* @access public
*/
check_mouse(event){
if(this.path){
/** @type {boolean} */
const is_over = this.kanvas.context.isPointInPath(this.path, event.clientX, event.clientY);
if(this.is_mouse_over){
if(!is_over){
this.is_mouse_over = false;
this.on_mouse_out.execute(this, event);
};
}else{
if(is_over){
this.is_mouse_over = true;
this.on_mouse_over.execute(this, event);
};
};
if(is_over)
this.on_mouse_move.execute(this, event);
else
this.draw_childs && this.childs.forEach(child => child && child.status && child.check_mouse(event));
};
};
/**
* @param {!EventMouse} event
* @param {!number} events
* @returns {void}
* @access public
*/
check_click(event, events){
if(!this.ignore_mouse_events){
if(this.path && this.kanvas.context.isPointInPath(this.path, event.clientX, event.clientY))
["click", "mouse_down", "mouse_up"].forEach((name, i) => {
events & (1 << i) && this["on_" + name].execute(this, event);
});
else
this.childs.forEach(child => {
child && child.check_click && child.check_click(event, events);
});
};
};
/**
* @param {!CanvasRenderingContext2D} context
* @param {!number} x
* @param {!number} y
* @returns {void}
* @access public
*/
predraw(context, x, y){
context.save();
context.beginPath();
this.matrix = new DOMMatrix(this.parent ? Kanvas.ObjectBase.transform_array(this.parent.matrix) : [1, 0, 0, 1, this.kanvas.x, this.kanvas.y]);
this.matrix.translateSelf(x, y);
if(this.rotation){
/** @type {number} */
const x = this.width ? this.kanvas._(this.width / 2) : 0,
/** @type {number} */
y = this.height ? this.kanvas._(this.height / 2) : 0;
this.matrix.translateSelf(x, y);
this.matrix.rotateSelf(this.rotation);
this.matrix.translateSelf(-x, -y);
};
this.background !== null && (context.fillStyle = this.background);
this.alpha !== null && (context.globalAlpha = this.alpha);
this.border_size && (context.lineWidth = this.kanvas._(this.border_size));
this.border_color && (context.strokeStyle = this.border_color);
};
/**
* @param {!CanvasRenderingContext2D} context
* @returns {void}
* @access public
*/
draw(context, x, y){};
/**
* @param {!CanvasRenderingContext2D} context
* @param {!Array.<Path2D>} [shape = []]
* @returns {void}
* @access public
*/
fill(context, shape = []){
(this.kanvas.shadow_cached || this.background) && context.fill(...shape);
this.border_size && context.stroke(...shape);
};
/**
* @param {!CanvasRenderingContext2D} context
* @param {!number} x
* @param {!number} y
* @returns {void}
* @access public
*/
postdraw(context, x, y){
if(this.path){
const shape = new Path2D();
shape.addPath(this.path, this.matrix);
this.fill(context, [this.path = shape]);
};
context.closePath();
this.draw_childs &&
this.childs.length &&
this.kanvas.draw(context, this.childs, this);
this.parent && this.parent.set_child_path(self.path);
if(this.shadows.length){};
context.restore();
};
/**
* @param {!CanvasRenderingContext2D} context
* @returns {void}
* @access public
*/
draw_level(context){
if(this.status & Kanvas.LOADED){
try{
/** @type {number} */
const x = this.kanvas._(this.x),
/** @type {number} */
y = this.kanvas._(this.y);
this.path = new Path2D();
this.predraw(context, x, y);
this.draw(context, x, y);
this.postdraw(context, x, y);
}catch(exception){
// console.error(exception);
};
};
};
/**
* @param {!Path2D} path
* @returns {void}
* @access public
*/
set_child_path(path){
this.path.addPath(new Path2D(path));
};
};
/** @type {Object.<string, ObjectBase>} */
Kanvas.SHAPES = {
/**
* @class
* @extends Kanvas.ObjectBase
* @access public
* @static
*/
Rectangle : class extends Kanvas.ObjectBase{
/**
* @constructor
* @param {!Kanvas} kanvas
* @param {!Kanvas.ObjectBase} parent
* @param {!(Object.<string, any|null>|Array)} inputs
* @param {?number} [i = null]
* @access public
*/
constructor(kanvas, parent, inputs, i = null){
super(kanvas, parent, inputs, i, {
number : ["x", "y", "width", "height", "radius"],
string : ["color"],
array : ["radius", "childs"]
});
this.type = Kanvas.SHAPES.Rectangle;
this.type_name = "Rectangle";
/** @type {Array.<number>} */
this.radius = Kanvas.get_array(this._get("radius", []));
/** @type {boolean} */
this.use_modern_mode = this._get("use_modern_mode", true);
this.set_status(Kanvas.LOADED);
};
/**
* @param {!CanvasRenderingContext2D} context
* @returns {void}
* @access public
*/
draw(context, x, y){
if(this.radius.every(radius => !radius))
this.path.rect(0, 0, this.kanvas._(this.width), this.kanvas._(this.height));
else{
if(this.use_modern_mode)
this.path.roundRect(0, 0, this.kanvas._(this.width), this.kanvas._(this.height), this.radius.map(radius => this.kanvas._(radius)));
else{
switch(this.radius.length){
case 1:
this.radius.push(...[0, 0, 0].map(_ => this.radius[0]));
break;
case 2:
this.radius.push(...this.radius);
break;
case 3:
this.radius.push(this.radius[1]);
break;
};
/** @type {number} */
const radius_top_left = this.kanvas._(this.radius[0]),
/** @type {number} */
radius_top_right = this.kanvas._(this.radius[1]),
/** @type {number} */
radius_bottom_right = this.kanvas._(this.radius[2]),
/** @type {number} */
radius_bottom_left = this.kanvas._(this.radius[3]),
/** @type {number} */
width = this.kanvas._(this.width),
/** @type {number} */
height = this.kanvas._(this.height);
this.path.moveTo(radius_top_left, 0);
this.path.lineTo(width - radius_top_right, 0);
this.path.quadraticCurveTo(width, 0, width, radius_top_right);
this.path.lineTo(width, height - radius_bottom_right);
this.path.quadraticCurveTo(width, height, width - radius_bottom_right, height);
this.path.lineTo(radius_bottom_left, height);
this.path.quadraticCurveTo(0, height, 0, height - radius_bottom_left);
this.path.lineTo(0, radius_top_left);
this.path.quadraticCurveTo(0, 0, radius_top_left, 0);
};
};
};
},
/**
* @class
* @extends Kanvas.ObjectBase
* @access public
* @static
*/
Ellipse : class extends Kanvas.ObjectBase{
/**
* @param {!Kanvas} kanvas
* @param {!Kanvas.ObjectBase} parent
* @param {!(Object.<string, any|null>|Array)} inputs
* @param {?number} [i = null]
* @access public
*/
constructor(kanvas, parent, inputs, i = null){
super(kanvas, parent, inputs, i, {}, {
number : ["x", "y", "radius", "from", "to"],
string : ["color"],
array : ["radius", "childs"]
});
this.type = Kanvas.SHAPES.Ellipse;
this.type_name = "Ellipse";
/** @type {number|Array.<number, number>} */
this.radius = this._get("radius");
/** @type {number} */
this.from = this._get(["from", "start"], 0);
/** @type {number} */
this.to = this._get(["to", "end"], 360);
this.set_status(Kanvas.LOADED);
};
/**
* @param {!CanvasRenderingContext2D} context
* @returns {void}
* @access public
*/
draw(context, x, y){
if(Kanvas.is_array(this.radius))
this.path.ellipse(0, 0, this.kanvas._(radius[0]), this.kanvas._(radius[1]), 0, Kanvas.to_radians(this.from), Kanvas.to_radians(this.to));
else
this.path.arc(0, 0, this.kanvas._(this.radius), Kanvas.to_radians(this.from), Kanvas.to_radians(this.to));
};
},
/**
* @class
* @extends Kanvas.ObjectBase
* @access public
* @static
*/
Text : class extends Kanvas.ObjectBase{
static x = {
left : 0,
right : 1,
center : .5,
start : 0,
end : 1
};
static y = {
top : 0,
hanging : .1,
middle : .5,
alphabetic : .8,
ideographic : .9,
bottom : 1
};
/**
* @param {!Kanvas} kanvas
* @param {!Kanvas.ObjectBase} parent
* @param {!(Object.<string, any|null>|Array)} inputs
* @param {?number} [i = null]
* @access public
*/
constructor(kanvas, parent, inputs, i = null){
super(kanvas, parent, inputs, i, {}, {
number : ["x", "y", "size", "baseline"],
string : ["text", "align", "font", "color", "baseline"],
array : ["childs"]
});
this.type = Kanvas.SHAPES.Text;
this.type_name = "Text";
/** @type {string} */
this.text = this._get("text");
/** @type {number} */
this.size = this._get("size", 1);
/** @type {string} */
this.font = this._get(["font", "family", "font_family"], this.kanvas.default_font_family);
/** @type {string|number|null} */
this.weight = this._get("weight", null);
/** @type {string|null} */
this.style = this._get("style", null);
/** @type {boolean} */
this.bold = this._get("bold", false);
/** @type {boolean} */
this.italic = this._get("italic", false);
/** @type {boolean} */
this.oblique = this._get("oblique", false);
/** @type {string|number} */
this.baseline = this._get(["baseline", "base_line", "vertical_align"], null);
/** @type {string} */
this.align = this._get("align", null);
this.set_status(Kanvas.LOADED);
};
/**
* @param {!CanvasRenderingContext2D} context
* @param {!number} x
* @param {!number} y
* @returns {void}
* @access public
*/
predraw(context, x, y){
super.predraw(context, x, y);
context.font = (
(this.weight ? this.weight + " " : this.bold ? "bold " : "") +
(this.style ? this.style + " " : this.italic ? "italic " : this.oblique ? "oblique " : "") +
this.kanvas._(this.size) + "px " +
this.font
);
this.align && (context.textAlign = this.align);
this.baseline && (context.textBaseline = this.baseline);
};
/**
* @param {!CanvasRenderingContext2D} context
* @returns {void}
* @access public
*/
draw(context, x, y){
/** @type {number} */
const width = context.measureText(this.text).width,
/** @type {number} */
height = this.kanvas._(this.size),
/** @type {number} */
_x = -Kanvas.SHAPES.Text.x[this.align || "start"] * width,
/** @type {number} */
_y = -Kanvas.SHAPES.Text.y[this.baseline || "alphabetic"] * height;
context.setTransform(...Kanvas.ObjectBase.transform_array(this.matrix));
context[this.background ? "fillText" : "strokeText"](this.text, 0, 0);
this.fill(context);
context.setTransform(1, 0, 0, 1, 0, 0);
this.path.moveTo(_x, _y);
this.path.lineTo(width + _x, _y);
this.path.lineTo(width + _x, height + _y);
this.path.lineTo(_x, height + _y);
};
postdraw(context, x, y){
this.background !== null && (context.fillStyle = "transparent");
if(!this.kanvas.shadow_cached){
this.border_size && (context.lineWidth = 0);
this.border_color && (context.strokeStyle = "transparent");
};
super.postdraw(context, x, y);
};
},
/**
* @class
* @extends Kanvas.ObjectBase
* @access public
* @static
*/
Image : class extends Kanvas.ObjectBase{
/**
* @returns {void}
* @access public
*/
set_size(){
this.width === null && (this.width = this.image.width);
this.height === null && (this.height = this.image.height);
};
/**
* @param {!Kanvas} kanvas
* @param {!Kanvas.ObjectBase} parent
* @param {!(Object.<string, any|null>|Array)} inputs
* @param {?number} [i = null]
* @access public
*/
constructor(kanvas, parent, inputs, i = null){
super(kanvas, parent, inputs, i, {
number : ["x", "y", "width", "height", "sub_x", "sub_y", "sub_width", "sub_height"],
string : ["source"],
array : ["childs"]
});
this.type = Kanvas.SHAPES.Image;
this.type_name = "Image";
/** @type {Image|null} */
this.image = this._get(["image", "object", "image_object", "image_item", "item"], null);
/** @type {string|null} */
this.source = this._get(["source", "src", "link", "url", "path"], null);
/** @type {number} */
this.sub_x = this._get(["sub_x", "subx", "_x"], null);
/** @type {number} */
this.sub_y = this._get(["sub_y", "suby", "_y"], null);
/** @type {number} */
this.sub_width = this._get(["sub_width", "_width"], null);
/** @type {number} */
this.sub_height = this._get(["sub_height", "_height"], null);
this.set_status(Kanvas.LOADING);
this.kanvas.preload_image(this.image || this.source, (image, ok) => {
this.image || (this.image = image);
if(ok){
this.set_size();
this.set_status(Kanvas.LOADED);
}else
this.set_status(Kanvas.ERROR);
});
};
/**
* @param {!CanvasRenderingContext2D} context
* @returns {void}
* @access public
*/
draw(context, x, y){
const width = this.kanvas._(this.width),
height = this.kanvas._(this.height);
this.path.rect(0, 0, width, height);
context.save();
context.setTransform(...Kanvas.ObjectBase.transform_array(this.matrix));
context.drawImage(...[this.image].concat((
this.sub_x !== null &&
this.sub_y !== null &&
this.sub_width !== null &&
this.sub_height !== null
) ? [
this.sub_x, this.sub_y, this.sub_width, this.sub_height
] : [], [0, 0, width, height]));
context.setTransform(1, 0, 0, 1, 0, 0);
context.restore();
};
},
/**
* @class
* @extends Kanvas.ObjectBase
* @access public
* @static
*/
Position : class extends Kanvas.ObjectBase{
/**
* @param {!Kanvas} kanvas
* @param {!Kanvas.ObjectBase} parent
* @param {!(Object.<string, any|null>|Array)} inputs
* @param {?number} [i = null]
* @access public
*/
constructor(kanvas, parent, inputs, i = null){
super(kanvas, parent, inputs, i, {
number : ["x", "y"],
array : ["childs"]
});
this.type = Kanvas.SHAPES.Position;
this.type_name = "Position";
this.status = Kanvas.LOADED;
};
},
/**
* @class
* @extends Kanvas.ObjectBase
* @access public
* @static
*/
Shape : class extends Kanvas.ObjectBase{
/** @type {Array.<string|null>} */
static methods = [null, null, "line", null, "quadraticCurve", "arc", "bezierCurve"];
/**
* @param {!Kanvas} kanvas
* @param {!Kanvas.ObjectBase} parent
* @param {!(Object.<string, any|null>|Array)} inputs
* @param {?number} [i = null]
* @access public
*/
constructor(kanvas, parent, inputs, i = null){
super(kanvas, parent, inputs, i, {
number : ["x", "y"],
array : ["shape", "childs"]
});
this.type = Kanvas.SHAPES.Shape;
this.type_name = "Shape";
/** @type {Array.<Array.<number>>} */
this.shape = this._get(["shape", "setps", "marks"], []).filter(dot => (
Kanvas.is_array(dot) &&
[2, 4, 5, 6].includes(dot.length) &&
dot.every(value => Kanvas.is_number(value))
));
this.status = Kanvas.LOADED;
};
/**
* @returns {void}
* @access public
*/
draw(context, x, y){
this.path.moveTo(0, 0);
this.shape.length > 2 && this.shape.forEach(dot => {
this.methods[dot.length] && this.path[this.methods[dot.length] + "To"](...dot);
});
};
},
/**
* @class
* @extends Kanvas.ObjectBase
* @access public
* @static
*/
Sprite : class extends Kanvas.ObjectBase{
/**
* @param {!Kanvas} kanvas
* @param {!Kanvas.ObjectBase} parent
* @param {!(Object.<string, any|null>|Array)} inputs
* @param {?number} [i = null]
* @access public
*/
constructor(kanvas, parent, inputs, i = null){
super(kanvas, parent, inputs, i, {
number : ["x", "y"],
string : ["sources"],
array : ["sources", "map", "childs"]
});
const images = this._get(["images", "image"], {}),
_i = this._get(["i", "index", "position"], 0);
this.type = Kanvas.SHAPES.Sprite;
this.type_name = "Sprite";
/** @type {Array.<string>} */
this.sources = Kanvas.get_array(this._get(["sources", "source", "src", "links", "urls", "paths", "link", "url", "path"], []));
/** @type {Object.<string, Image>} */
this.images = (
Kanvas.is_dictionary(images) ? images :
(Kanvas.is_array(images) ? images : [images]).reduce((dictionary, image) => {
if(Kanvas.is_image(image))
dictionary[image.src] = image;
else if(Kanvas.is_string(image) && !this.sources.includes(image))
this.sources.push(image);
return dictionary;
}, {})
);
/** @type {Object.<string, Array.<Kanvas.SpritesMap>>} */
this.map = Kanvas.SpritesMap.process(this._get("map"), source => {
this.sources.includes(source) || this.sources.push(source)
});
/** @type {string|null} */
this.group = this._get("group", null);
/** @type {number} */
this.i = 0;
/** @type {number} */
this.frames_per_second = this._get(["frames_per_second", "fps"], 12);
/** @type {boolean} */
this.autoplay = this._get("autoplay", true);
/** @type {number} */
this.last_next = 0;
_i === null || this.next(_i);
this.status = Kanvas.LOADING;
Kanvas.execute_array(this.sources, (source, callback) => {
Kanvas.load_image(source, (image, ok) => {
this.images[source] = ok ? image : null;
Kanvas.execute(callback);
});
}, () => {
this.status = Kanvas.LOADED;
});
};
/**
* @param {!string} group
* @param {!number} [i = 0]
* @param {boolean|null} [autoplay = null]
* @returns {void}
* @access public
*/
set(group, i = 0, autoplay = null){
this.last_i_time = performance.now();
this.group = group;
this.autoplay = autoplay === null ? this.autoplay : autoplay;
this.next(i);
};
next(i = null){
this.i = i === null ? (this.i + i) % this.map[this.group].length : i;
this.width = this.map[this.group][this.i].width;
this.height = this.map[this.group][this.i].height;
};
/**
* @returns {void}
* @access public
*/
draw(context, x, y){
if(this.group && this.map[this.group]){
if(this.autoplay && this.map[this.group].length > 1){
const next = performance.now() / (1000 / this.frames_per_second) >> 0;
if(next != this.last_next){
this.next((this.i + next - this.last_next) % this.map[this.group].length);
this.last_next = next;
};
};
/** @type {Kanvas.SpritesMap} */
const sprite = this.map[this.group][this.i],
x = this.kanvas._(sprite.x),
y = this.kanvas._(sprite.y),
width = this.kanvas._(sprite.width),
height = this.kanvas._(sprite.height);
this.path = new Path2D();
this.path.rect(x, y, width, height);
if(this.images[sprite.source]){
context.save();
context.setTransform(...Kanvas.ObjectBase.transform_array(this.matrix));
context.drawImage(
this.images[sprite.source],
sprite.sub_x, sprite.sub_y, sprite.sub_width, sprite.sub_height,
0, 0, width, height
);
context.setTransform(1, 0, 0, 1, 0, 0);
context.restore();
};
};
};
},
/**
* @class
* @extends Kanvas.ObjectBase
* @access public
* @static
*/
Cache : class extends Kanvas.ObjectBase{
/**
* @param {!Kanvas} kanvas
* @param {!Kanvas.ObjectBase} parent
* @param {!(Object.<string, any|null>|Array)} inputs
* @param {?number} [i = null]
* @access public
*/
constructor(kanvas, parent, inputs, i = null){
super(kanvas, parent, inputs, i, {
number : ["x", "y", "width", "height", "x_position", "y_position"],
array : ["childs"]
});
/** @type {number} */
this.margin_x = this._get(["x_position", "sub_x", "subx", "xposition", "margin_x", "marginx"], 0);
/** @type {number} */
this.margin_y = this._get(["y_position", "sub_y", "suby", "yposition", "margin_y", "marginy"], 0);
/** @type {HTMLCanvasElement} */
this.subcanvas = document.createElement("canvas");
/** @type {CanvasRenderingContext2D} */
this.subcontext = this.subcanvas.getContext("2d");
/** @type {boolean} */
this.cache_full_loaded = false;
this.on_full_done.add(() => {
this.cache_full_loaded = true;
});
this.kanvas.on_change_screen.add(() => {
this.cache_full_loaded = false;
});
this.status = Kanvas.LOADED;
};
/**
* @returns {void}
* @access public
*/
rebuild(){
this.cache_full_loaded = false;
};
/**
* @returns {void}
* @access public
*/
draw(context, x, y){
if(this.cache_full_loaded)
context.drawImage(this.subcanvas, x - this.margin_x, y - this.margin_y);
else
this.kanvas.draw(this.subcontext, this.childs, this);
};
}
};
/**
* @class
* @constructor
* @param {!(string|number|Array.<string|number>|Object.<string, string|number>)} inputs
* @returns {void}
* @access public
* @static
*/
Kanvas.SpritesMap = (function(){
/**
* @constructs Kanvas.SpritesMap
* @param {!(string|number|Array.<string|number>|Object.<string, string|number>)} inputs
* @returns {void}
* @access private
*/
const SpritesMap = function(...inputs){
/** @type {Kanvas.SpritesMap} */
const self = this;
/** @type {string|null} */
this.source = null;
/** @type {number} */
this.sub_x = 0;
/** @type {number} */
this.sub_y = 0;
/** @type {number} */
this.sub_width = 0;
/** @type {number} */
this.sub_height = 0;
/** @type {number} */
this.x = 0;
/** @type {number} */
this.y = 0;
/** @type {number} */
this.width = 0;
/** @type {number} */
this.height = 0;
const constructor = () => {
if(inputs.length){
inputs.length == 1 && (inputs = inputs[0]);
if(Kanvas.is_array(inputs))
[self.source, self.sub_x, self.sub_y, self.sub_width, self.sub_height, self.width, self.height, self.x, self.y] = inputs;
else if(Kanvas.is_dictionary(inputs)){
self.source = Kanvas.get_value(["source", "src", "link", "url", "path"], inputs, self.source);
self.sub_x = Kanvas.get_value(["sub_x", "subx", "left", "horizontal"], inputs, self.sub_x);
self.sub_y = Kanvas.get_value(["sub_y", "suby", "top", "vertical"], inputs, self.sub_y);
self.sub_width = Kanvas.get_value("sub_width", inputs, self.sub_width);
self.sub_height = Kanvas.get_value("sub_height", inputs, self.sub_height);
self.width = Kanvas.get_value("width", inputs, self.width || self.sub_width);
self.height = Kanvas.get_value("height", inputs, self.height || self.sub_height);
self.x = Kanvas.get_value("width", inputs, self.x);
self.y = Kanvas.get_value("height", inputs, self.y);
};
};
};
constructor();
};
/**
* @param {!Object.<string, Array.<Array.<string|number>|Kanvas.SpritesMap>>} map
* @param {!kanvas_source_callback} [source_callback]
* @returns {Object.<string, Array.<Kanvas.SpritesMap>>}
* @access public
* @static
*/
SpritesMap.process = (map, source_callback) => {
/** @type {Object.<string, Array.<Kanvas.SpritesMap>>} */
const processed = {};
for(const key in map)
if(Kanvas.is_array(map[key])){
processed[key] = [];
map[key].forEach(sprite => {
if(Kanvas.is_array(sprite) && sprite[1] == "pattern"){
/** @type {number} */
const l = sprite.length,
/** @type {[string, number, number, number, number, number, number, number, number, number, number, number, number]} */
[source, sub_width, sub_height, sub_x, sub_y, width, height, x, y, margin_x, margin_y, padding_x, padding_y, length] = [
sprite[0],
...sprite.slice(2, 4),
...(l > 5 ? sprite.slice(4, 6) : [0, 0]),
...(l > 7 ? sprite.slice(6, 8) : [0, 0]),
...(l > 9 ? sprite.slice(8, 10) : [0, 0]),
...(l > 11 ? sprite.slice(10, 11) : [0, 0]),
...(l > 13 ? sprite.slice(12, 14) : [0, 0]),
...(l % 2 ? sprite.slice(-1) : [0])
];
/** @type {number} */
let k = 0,
/** @type {boolean} */
ended = false;
Kanvas.execute(source_callback, source);
for(let i = 0; i < sub_x && !ended; i ++)
for(let j = 0; j < sub_y; j ++){
processed[key].push(new Kanvas.SpritesMap([
source,
padding_x + j * sub_width + j * margin_x,
padding_y + i * sub_height + i * margin_y,
sub_width,
sub_height,
width,
height,
x,
y
]));
if(length && ++ k == length){
ended = true;
break;
};
};
}else
processed[key].push(sprite instanceof Kanvas.SpritesMap ? sprite : new Kanvas.SpritesMap(sprite));
});
};
return processed;
};
return SpritesMap;
})();
/** @type {number} */
Kanvas.OVERWRITE = 1 << 0;
/** @type {number} */
Kanvas.NULLS = 1 << 1;
/** @type {number} */
Kanvas.NO_EMPTY = 1 << 0;
/** @type {number} */
Kanvas.NO_TRIM = 1 << 1;
/** @type {number} */
Kanvas.KEY = 1 << 2;
/** @type {number} */
Kanvas.THREADS_MODE_INTERVAL = 1 << 0;
/** @type {number} */
Kanvas.THREADS_MODE_TIMEOUT = 1 << 1;
/** @type {number} */
Kanvas.THREADS_MODE_ANIMATION = 1 << 2;
/** @type {number} */
Kanvas.ONCE = 1 << 0;
/** @type {number} */
Kanvas.AUTOEXECUTE = 1 << 1;
/** @type {number} */
Kanvas.UNLOADED = 1 << 0;
/** @type {number} */
Kanvas.LOADING = 1 << 1;
/** @type {number} */
Kanvas.LOADED = 1 << 2;
/** @type {number} */
Kanvas.ERROR = 1 << 3;
/** @type {number} */
Kanvas.UNBUILT = 1 < 4;
/** @type {number} */
Kanvas.THREADS_MODE_ANIMATION = 1 << 0;
/** @type {number} */
Kanvas.THREADS_MODE_INTERVAL = 1 << 1;
/** @type {number} */
Kanvas.THREADS_MODE_TIMEOUT = 1 << 2;
/** @type {number} */
Kanvas.MOUSE_CLICK = 1 << 0;
/** @type {number} */
Kanvas.MOUSE_DOWN = 1 << 1;
/** @type {number} */
Kanvas.MOUSE_UP = 1 << 2;
/**
* @param {?any} item
* @returns {boolean}
* @access public
* @static
*/
Kanvas.is_array = item => item instanceof Array;
/**
* @param {?any} item
* @returns {boolean}
* @access public
* @static
*/
Kanvas.is_dictionary = item => item && item.constructor == Object;
/**
* @param {!(Object.<string, any|null>|Array.<any|null>)} items
* @param {!number} [options = 0]
* @returns {Object.<string, any|null>}
* @access public
* @static
*/
Kanvas.get_dictionary = (items, options = 0) => (
Kanvas.is_array(items) ? items.reduce(options & Kanvas.OVERWRITE ? (dictionary, subitem) => ({
...dictionary,
...Kanvas.get_dictionary(subitem)
}) : (dictionary, subitem) => ({
...Kanvas.get_dictionary(subitem),
...dictionary
}), {}) :
Kanvas.is_dictionary(items) ? items :
{});
/**
* @param {?any} item
* @param {!number} [options = 0]
* @returns {boolean}
* @access public
* @static
*/
Kanvas.is_string = (item, options = 0) => {
if(typeof item == "string"){
/** @type {boolean} */
const no_empty = !!((Kanvas.NO_EMPTY | Kanvas.KEY) & options);
if(
(!no_empty || item) &&
(!((Kanvas.NO_TRIM | Kanvas.KEY) & options) || item.trim() == item) &&
(!no_empty || item.trim()) &&
(!(Kanvas.KEY & options) || /^[a-z0-9_]+$/i.test(item))
)
return true;
};
return false;
};
/**
* @param {?any} items
* @returns {Array.<any|null>}
* @access public
* @static
*/
Kanvas.get_array = items => Kanvas.is_array(items) ? items : [items];
/**
* @param {?any} items
* @returns {Array.<string>}
* @access public
* @static
*/
Kanvas.get_keys = items => Kanvas.get_array(items).filter((key, i, keys) => (
Kanvas.is_string(key, Kanvas.KEY) &&
keys.indexOf(key) == i
));
/**
* @param {!Array.<string>} keys
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @param {?any} _default
* @param {!boolean} nulls
* @returns {any|null}
* @access public
* @static
*/
Kanvas.process_get_value = (keys, inputs, _default, nulls) => {
/** @type {boolean} */
let ok = false,
/** @type {any|null} */
response = _default;
if(keys.length){
if(Kanvas.is_dictionary(inputs)){
/** @type {number} */
const l = keys.length;
for(let i = 0; i < l; i ++)
if(inputs[keys[i]] !== undefined && (nulls || inputs[keys[i]] !== null)){
response = inputs[keys[i]];
ok = true;
};
}else if(Kanvas.is_array(inputs))
inputs.some(subinputs => {
[response, ok] = Kanvas.process_get_value(keys, subinputs, _default, nulls);
return ok;
});
};
return [ok ? response : _default, ok];
};
/**
* @param {!(string|Array.<string>)} keys
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
* @param {?any} [_default = null]
* @param {!number} [options = 0]
* @returns {any|null}
* @access public
* @static
*/
Kanvas.get_value = (keys, inputs, _default = null, options = 0) => {
return Kanvas.process_get_value(Kanvas.get_keys(keys), inputs, _default, !!(Kanvas.NULLS & options))[0];
};
/**
* @param {?any} item
* @returns {boolean}
* @access public
* @static
*/
Kanvas.is_function = item => typeof item == "function";
/**
* @param {?any} item
* @returns {boolean}
* @access public
* @static
*/
Kanvas.is_number = item => typeof item == "number";
/**
* @param {?any} item
* @returns {boolean}
* @access public
* @static
*/
Kanvas.is_integer = item => Kanvas.is_number(item) && item == item >> 0;
/**
* @param {?any} item
* @returns {boolean}
* @access public
* @static
*/
Kanvas.is_index = item => Kanvas.is_integer(item) && item >= 0;
/**
* @param {?any} item
* @returns {boolean}
* @access public
* @static
*/
Kanvas.is_html_item = item => item && (item.tagName || item.nodeName);
/**
* @param {!kanvas_execute_callback} callback
* @param {...any} [parameters]
* @returns {any|null}
* @access public
* @static
*/
Kanvas.execute = (callback, ...parameters) => Kanvas.is_function(callback) ? callback(...parameters) : null;
/**
* @param {!Array.<any|null>} array
* @param {!kanvas_array_item_callback} callback_per_item
* @param {!kanvas_default_callback} [callback_end = null]
* @returns {void}
* @access public
* @static
*/
Kanvas.execute_array = (array, callback_per_item, callback_end = null) => {
/** @type {number} */
const l = array.length;
if(!l){
Kanvas.execute(callback_end);
return;
};
/** @type {number} */
let loaded = 0;
array.forEach(item => {
Kanvas.execute(callback_per_item, item, () => {
++ loaded == l && Kanvas.execute(callback_end);
});
});
};
/**
* @param {!number} degrees
* @returns {number}
* @access public
* @static
*/
Kanvas.to_radians = degrees => 2 * Math.PI * degrees / 360;
/**
* @param {!string} source
* @param {!kanvas_load_image_callback} callback
* @returns {void}
* @access public
* @static
*/
Kanvas.load_image = (source, callback) => {
/** @type {Image} */
const image = new Image();
image.src = source;
image.onload = () => callback(image, true);
image.onerror = () => callback(image, false);
};
/**
* @param {any|null} item
* @returns {boolean}
* @access public
* @static
*/
Kanvas.is_image = item => item instanceof Image;
return Kanvas;
})();