2535 lines
84 KiB
JavaScript
2535 lines
84 KiB
JavaScript
"use strict";
|
|
|
|
/**
|
|
* @callback kanvas_default_callback
|
|
* @returns {void}
|
|
*/
|
|
|
|
/**
|
|
* @callback kanvas_thread_callback
|
|
* @param {Kanvas.ThreadModel} thread
|
|
* @returns {void}
|
|
*/
|
|
|
|
/**
|
|
* @callback kanvas_preload_callback
|
|
* @param {!HTMLElement} selector
|
|
* @param {!boolean} asynchronous
|
|
* @returns {void}
|
|
*/
|
|
|
|
/**
|
|
* @callback kanvas_event_callback
|
|
* @param {!number} i
|
|
* @param {...any} [parameters]
|
|
* @returns {any|null|void}
|
|
*/
|
|
|
|
/**
|
|
* @callback kanvas_event_mouse_callback
|
|
* @param {!number} i
|
|
* @param {!number} x
|
|
* @param {!number} y
|
|
* @param {...any} [parameters]
|
|
* @returns {any|null|void}
|
|
*/
|
|
|
|
/**
|
|
* @callback kanvas_on_change_callback
|
|
* @param {Kanvas.ObjectBase|null} child
|
|
* @returns {void}
|
|
*/
|
|
|
|
/**
|
|
* @callback kanvas_load_image_callback
|
|
* @param {!Image} image
|
|
* @param {!boolean} ok
|
|
* @returns {void}
|
|
*/
|
|
|
|
/**
|
|
* @callback kanvas_execute_callback
|
|
* @param {...(any|null)} [parameters]
|
|
* @returns {any|null}
|
|
*/
|
|
|
|
/**
|
|
* @callback kanvas_source_callback
|
|
* @param {string} source
|
|
*/
|
|
|
|
/**
|
|
* @callback kanvas_array_item_callback
|
|
* @param {any|null} item
|
|
* @param {!kanvas.kanvas_default_callback} callback
|
|
* @returns {void}
|
|
*/
|
|
|
|
/**
|
|
* @class
|
|
* @constructor
|
|
* @param {Object.<string, any|null>|Array.<Object.<string, any|null>>} inputs
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
export const Kanvas = (function(){
|
|
|
|
/**
|
|
* @constructs Kanvas
|
|
* @param {Object.<string, any|null>|Array.<Object.<string, any|null>>} custom
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
const Kanvas = function(custom){
|
|
|
|
/** @type {Kanvas} */
|
|
const self = this,
|
|
/** @type {Object.<string, any|null>} */
|
|
default_settings = {},
|
|
/** @type {Array.<number>} */
|
|
threads = [],
|
|
/** @type {Array.<number>} */
|
|
thread_times = [],
|
|
/** @type {Kanvas.Screen} */
|
|
screen = new Kanvas.Screen(),
|
|
/** @type {Object.<string, Image>} */
|
|
images_cache = {};
|
|
/** @type {number} */
|
|
let last_time = 0,
|
|
/** @type {number} */
|
|
thread_times_summatory = 0,
|
|
/** @type {number|null} */
|
|
draw_thread_i = null,
|
|
/** @type {boolean} */
|
|
screen_done = false,
|
|
/** @type {number} */
|
|
last_thread_iteration = 0;
|
|
|
|
/** @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.delta = 0;
|
|
/** @type {number} */
|
|
this.cells = 40;
|
|
/** @type {number} */
|
|
this.quality = 1;
|
|
/** @type {HTMLCanvasElement|null} */
|
|
this.canvas = null;
|
|
/** @type {CanvasRenderingContext2D|null} */
|
|
this.context = null;
|
|
/** @type {number} */
|
|
this.preload_timeout = 2000;
|
|
/** @type {string} */
|
|
this.default_font_family = "monospace";
|
|
/** @type {number} */
|
|
this.x = 0;
|
|
/** @type {number} */
|
|
this.y = 0;
|
|
/** @type {number} */
|
|
this.height = 0;
|
|
/** @type {number} */
|
|
this.width = 0;
|
|
/** @type {number} */
|
|
this.cells_x = 0;
|
|
/** @type {number} */
|
|
this.cells_y = 0;
|
|
/** @type {number} */
|
|
this.cell_size = 0;
|
|
/** @type {number} */
|
|
this.start_x = .5;
|
|
/** @type {number} */
|
|
this.start_y = .5;
|
|
/** @type {HTMLElement|null} */
|
|
this.cache = null;
|
|
/** @type {HTMLElement|null} */
|
|
this.item_self = null;
|
|
/** @type {number} */
|
|
this.threads_mode = Kanvas.THREADS_MODE_ANIMATION;
|
|
/** @type {number} */
|
|
this.difference = 0;
|
|
/** @type {string} */
|
|
this.context = "2d";
|
|
|
|
/** @type {Array.<any|null>} */
|
|
this.map = [];
|
|
/** @type {boolean} */
|
|
this.shadow_cached = false;
|
|
/** @type {Kanvas.Event} */
|
|
this.on_ready = new Kanvas.Event(true);
|
|
/** @type {Kanvas.Event} */
|
|
this.on_change_screen = new Kanvas.Event();
|
|
|
|
/** @type {Kanvas.Event} */
|
|
this.on_mouse_down = new Kanvas.EventMouse();
|
|
/** @type {Kanvas.Event} */
|
|
this.on_mouse_up = new Kanvas.EventMouse();
|
|
/** @type {Kanvas.Event} */
|
|
this.on_click = new Kanvas.EventMouse();
|
|
/** @type {Kanvas.Event} */
|
|
this.on_mouse_over = new Kanvas.EventMouse();
|
|
/** @type {Kanvas.Event} */
|
|
this.on_mouse_out = new Kanvas.EventMouse();
|
|
/** @type {Kanvas.Event} */
|
|
this.on_mouse_move = new Kanvas.EventMouse();
|
|
/** @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();
|
|
|
|
/**
|
|
* @returns {void}
|
|
* @access private
|
|
*/
|
|
const constructor = () => {
|
|
|
|
/** @type {kanvas_event_callback|null} */
|
|
const on_ready_event = self.settings("on_ready");
|
|
|
|
self.preload_timeout = self.settings(["preload_timeout", "timeout"], null, self.preload_timeout);
|
|
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);
|
|
self.default_font_family = self.settings("default_font_family", null, self.default_font_family);
|
|
self.start_x = self.settings("start_x", null, self.start_x);
|
|
self.start_y = self.settings("start_y", null, self.start_y);
|
|
self.threads_mode = self.settings("threads_mode", null, self.threads_mode);
|
|
self.context = self.settings("context", null, self.context);
|
|
|
|
threads_run();
|
|
self.thread_add(check_screen);
|
|
|
|
Kanvas.is_function(on_ready_event) && self.on_ready.add(on_ready_event);
|
|
|
|
self.preload(self.settings("position"), (position, asynchronous) => {
|
|
if(position){
|
|
|
|
if(position.tagName.toLowerCase() != "canvas")
|
|
self.canvas = (
|
|
self.item_self = position.appendChild(document.createElement("span"))
|
|
).appendChild(document.createElement("canvas"));
|
|
else
|
|
self.item_self = (self.canvas = position).parentNode;
|
|
self.item_self.classList.contains("kanvas") || self.item_self.classList.add("kanvas");
|
|
(self.cache = self.item_self.querySelector(".cache")) || (
|
|
self.cache = self.item_self.appendChild(document.createElement("span"))
|
|
).classList.add("cache");
|
|
|
|
start();
|
|
|
|
};
|
|
});
|
|
|
|
};
|
|
|
|
/**
|
|
* @param {!Event} event
|
|
* @param {!string} name
|
|
* @returns {void}
|
|
* @access private
|
|
*/
|
|
const check_event = (event, name) => {
|
|
|
|
/** @type {Kanvas.Event} */
|
|
const event_manager = self["on_" + name];
|
|
|
|
if(event_manager){
|
|
if(event instanceof MouseEvent || event instanceof PointerEvent)
|
|
event_manager.execute(
|
|
(event.clientX - self.width * self.start_x) / self.cell_size,
|
|
(event.clientY - self.height * self.start_y) / self.cell_size
|
|
);
|
|
else if(event instanceof KeyboardEvent){
|
|
event.key == "Tab" && event.preventDefault();
|
|
event_manager.execute(event.key);
|
|
};
|
|
};
|
|
|
|
};
|
|
|
|
/**
|
|
* @returns {void}
|
|
* @access private
|
|
*/
|
|
const start = () => {
|
|
self.thread_add({
|
|
bucle : false,
|
|
start_now : false,
|
|
callback : () => {
|
|
|
|
self.context = self.canvas.getContext(self.context, {
|
|
// willReadFrequently : true
|
|
});
|
|
|
|
self.canvas.setAttribute("tabindex", 0);
|
|
self.canvas.focus();
|
|
// [
|
|
// "mouse_down", "mouse_up", "click", "mouse_over", "mouse_out", "mouse_move",
|
|
// "key_down", "key_up", "key_press"
|
|
// ].forEach(event_name => {
|
|
// self["on_" + event_name] && self.canvas.addEventListener(event_name.replace(/[^a-z]+/g, ""), event => {
|
|
// check_event(event, event_name);
|
|
// if(event_name == "click")
|
|
// this.map.forEach(item => item && item.status && item.check_click(event));
|
|
// else if(!event_name.includes("key")){
|
|
// this.map.forEach(item => item && item.status && item["on_" + event_name].execute(item, event))
|
|
// });
|
|
// });
|
|
|
|
self.canvas.addEventListener("mousemove", event => {
|
|
self.on_mouse_move.execute(event);
|
|
self.map.forEach(item => item && item.status && item.check_mouse(event));
|
|
});
|
|
self.canvas.addEventListener("click", event => {
|
|
self.on_click.execute(event);
|
|
self.map.forEach(item => item && item.status && item.check_click(event));
|
|
});
|
|
|
|
draw_thread_i = self.thread_add(thread => {
|
|
|
|
/** @type {number} */
|
|
const x = self._(self.start_x * self.cells_x),
|
|
/** @type {number} */
|
|
y = self._(self.start_y * self.cells_y);
|
|
|
|
self.x = self._(self.start_x * self.cells_x);
|
|
self.y = self._(self.start_y * self.cells_y);
|
|
|
|
self.context.clearRect(0, 0, self.width, self.height >> 0);
|
|
// self.context.translate(x, y);
|
|
self.draw(self.context);
|
|
// self.context.translate(-x, -y);
|
|
|
|
});
|
|
|
|
const wait_for_screen = self.thread_add({
|
|
callback : () => {
|
|
if(screen_done){
|
|
self.thread_remove(wait_for_screen);
|
|
self.on_ready.autoexecute = true;
|
|
self.on_ready.execute();
|
|
};
|
|
},
|
|
bucle : true,
|
|
start_now : true
|
|
});
|
|
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @param {!(string|Array.<string>)} keys
|
|
* @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
|
|
* @param {any|null} [_default = null]
|
|
* @returns {any|null}
|
|
* @access public
|
|
*/
|
|
this.settings = (keys, inputs = null, _default = null) => Kanvas.get_value(keys, [inputs, custom, default_settings], _default);
|
|
|
|
/**
|
|
* @returns {void}
|
|
* @access private
|
|
*/
|
|
const check_screen = () => {
|
|
if(!self.canvas)
|
|
return;
|
|
screen_done || (screen_done = true);
|
|
|
|
if(
|
|
screen.x != self.canvas.offsetWidth ||
|
|
screen.y != self.canvas.offsetHeight ||
|
|
screen.quality != self.quality
|
|
){
|
|
|
|
screen.x = self.canvas.offsetWidth;
|
|
screen.y = self.canvas.offsetHeight;
|
|
screen.quality = self.quality;
|
|
|
|
self.canvas.setAttribute("width", self.width = screen.x * self.quality);
|
|
self.canvas.setAttribute("height", self.height = screen.y * self.quality);
|
|
|
|
self.cell_size = self.quality * (screen.x < screen.y ? screen.x : screen.y) / self.cells;
|
|
self.cells_x = self.width / self.cell_size;
|
|
self.cells_y = self.height / self.cell_size;
|
|
|
|
self.on_change_screen.execute();
|
|
|
|
};
|
|
|
|
};
|
|
|
|
const rerun_threads = () => {
|
|
const timeout = setTimeout(() => {
|
|
clearTimeout(timeout);
|
|
threads_run();
|
|
}, 1);
|
|
};
|
|
|
|
/**
|
|
* @returns {void}
|
|
* @access private
|
|
*/
|
|
const threads_run = () => {
|
|
switch(self.threads_mode){
|
|
case Kanvas.THREADS_MODE_INTERVAL:
|
|
|
|
/** @type {number} */
|
|
const current_frames_per_second = self.frames_per_second,
|
|
/** @type {number} */
|
|
interval = setInterval(() => {
|
|
thread_method();
|
|
if(
|
|
!(self.threads_mode & Kanvas.THREADS_MODE_INTERVAL) ||
|
|
current_frames_per_second != self.frames_per_second
|
|
){
|
|
clearInterval(interval);
|
|
threads_run();
|
|
};
|
|
}, self.frames_per_second ? 1000 / self.frames_per_second : 0);
|
|
|
|
break;
|
|
case Kanvas.THREADS_MODE_TIMEOUT:
|
|
setTimeout(() => {
|
|
thread_method();
|
|
threads_run();
|
|
}, self.frames_per_second ? 1000 / self.frames_per_second : 0);
|
|
break;
|
|
case Kanvas.THREADS_MODE_ANIMATION:
|
|
default:
|
|
requestAnimationFrame(() => {
|
|
|
|
/** @type {number} */
|
|
const time = Kanvas.get_time();
|
|
|
|
if(!self.frames_per_second || time - last_thread_iteration >= 1000 / self.frames_per_second){
|
|
last_thread_iteration = time;
|
|
thread_method();
|
|
};
|
|
|
|
threads_run();
|
|
|
|
});
|
|
break;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @returns {void}
|
|
* @access private
|
|
*/
|
|
const thread_method = () => {
|
|
|
|
/** @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 / (1 / self.real_frames_per_second) / 10000;
|
|
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;
|
|
|
|
threads.forEach(thread => {
|
|
thread && thread.execute();
|
|
});
|
|
|
|
};
|
|
|
|
/**
|
|
* @param {!(kanvas_thread_callback|Object.<string, number|boolean|kanvas_thread_callback>)} inputs
|
|
* @returns {number|null}
|
|
* @access public
|
|
*/
|
|
this.thread_add = inputs => {
|
|
|
|
/** @type {number} */
|
|
const thread = new Kanvas.ThreadModel(this, inputs);
|
|
/** @type {number|null} */
|
|
let i = null;
|
|
|
|
if(thread.ok()){
|
|
|
|
/** @type {number} */
|
|
const l = threads.length;
|
|
|
|
for(i = 0; i < l; i ++)
|
|
if(!threads[i])
|
|
break;
|
|
|
|
(threads[i] = thread).i = i;
|
|
|
|
};
|
|
|
|
return i;
|
|
};
|
|
|
|
/**
|
|
* @param {!(number)} i
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
this.thread_remove = i => {
|
|
threads[i] && (threads[i] = null);
|
|
};
|
|
|
|
/**
|
|
* @param {!number} size
|
|
* @returns {number}
|
|
* @access public
|
|
*/
|
|
this._ = size => size * self.cell_size;
|
|
|
|
/**
|
|
* @param {!CanvasRenderingContext2D} context
|
|
* @param {Array.<Kanvas.ObjectBase|Array.<any|null>|Object.<string, any|null>|null} [level = null]
|
|
* @param {kanvas_on_change_callback|null} [on_change = null]
|
|
* @param {?Kanvas.ObjectBase} [parent = null]
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
this.draw = (context, level = null, on_change = null, parent = null) => {
|
|
(level || (level = self.map)).forEach((item, i) => {
|
|
if(item){
|
|
if(item.draw_level){
|
|
item.draw_level(context);
|
|
// on_change && item.on_load.add(() => {
|
|
// console.log("PASA CHANGE")
|
|
// on_change(item);
|
|
// });
|
|
}else if(typeof item == "object"){
|
|
|
|
/** @type {string|Class} */
|
|
const type = Kanvas.is_array(item) ? item[0] : item.type;
|
|
|
|
if(level[i] = (
|
|
Kanvas.is_string(type) && Kanvas.SHAPES[type] ? new Kanvas.SHAPES[type](self, parent, item, i) :
|
|
Kanvas.is_class(type) ? new type(self, parent, item, i) :
|
|
null)){
|
|
level[i].draw_level(context);
|
|
if(Kanvas.is_function(on_change)){
|
|
level[i].on_status_change.add(on_change);
|
|
on_change(level[i]);
|
|
};
|
|
}else
|
|
Kanvas.is_function(on_change) && on_change(null);
|
|
|
|
};
|
|
};
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @param {!(string|HTMLElement)} selector
|
|
* @param {!kanvas_preload_callback} callback
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
this.preload = (selector, callback) => {
|
|
if(!Kanvas.is_function(callback))
|
|
return;
|
|
|
|
if(Kanvas.is_string(selector)){
|
|
|
|
/** @type {HTMLElement|null} */
|
|
let item;
|
|
|
|
try{
|
|
if(item = document.querySelector(selector)){
|
|
callback(item, false);
|
|
return;
|
|
};
|
|
}catch(exception){
|
|
callback(null, false);
|
|
return;
|
|
};
|
|
|
|
/** @type {number} */
|
|
const time = Kanvas.get_time(),
|
|
/** @type {number} */
|
|
preload = self.thread_add(thread => {
|
|
if(item = document.querySelector(selector)){
|
|
self.thread_remove(preload);
|
|
callback(item, true);
|
|
}else if(Kanvas.get_time() - time > self.preload_timeout){
|
|
self.thread_remove(preload);
|
|
callback(null, true);
|
|
};
|
|
});
|
|
|
|
}else if(Kanvas.is_html_object(selector))
|
|
callback(selector, false);
|
|
else
|
|
callback(null, false);
|
|
|
|
};
|
|
|
|
/**
|
|
* @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();
|
|
|
|
};
|
|
|
|
/**
|
|
* @returns {number}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.get_time = () => Date.now();
|
|
// Kanvas.get_time = () => performance.now();
|
|
|
|
/**
|
|
* @constructor
|
|
* @param {!(kanvas_thread_callback|Object.<string, number|boolean|kanvas_thread_callback>)} inputs
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
Kanvas.ThreadModel = function(kanvas, inputs){
|
|
|
|
Kanvas.is_function(inputs) && (inputs = {callback : inputs});
|
|
|
|
/** @type {Kanvas.ThreadModel} */
|
|
const self = this,
|
|
/** @type {kanvas_thread_callback} */
|
|
callback = Kanvas.get_value(["threads_callback", "callback"], inputs),
|
|
/** @type {boolean} */
|
|
is_function = Kanvas.is_function(callback);
|
|
/** @type {number} */
|
|
let iterations = 0;
|
|
|
|
/** @type {boolean} */
|
|
this.bucle = Kanvas.get_value(["threads_bucle", "bucle"], inputs, true);
|
|
/** @type {number} */
|
|
this.timer = Kanvas.get_value(["threads_timer", "timer"], inputs, 16.6667);
|
|
/** @type {number} */
|
|
this.last_time = Kanvas.get_value(["threads_start_now", "start_now"], inputs, true) ? 0 : Kanvas.get_time();
|
|
/** @type {number|null} */
|
|
this.i = null;
|
|
/** @type {boolean} */
|
|
this.play = Kanvas.get_value(["threads_autostart", "autostart"], inputs, true);
|
|
|
|
/**
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
this.execute = () => {
|
|
if(is_function && self.play){
|
|
|
|
/** @type {number} */
|
|
const time = Kanvas.get_time();
|
|
|
|
if(iterations && !self.bucle)
|
|
kanvas.thread_remove(self.i);
|
|
else if(time - self.last_time >= self.timer){
|
|
self.last_time = time;
|
|
callback(self);
|
|
iterations ++;
|
|
};
|
|
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
this.retry = () => {
|
|
iterations = 0;
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @returns {boolean}
|
|
* @access public
|
|
*/
|
|
this.ok = () => is_function;
|
|
|
|
};
|
|
|
|
/** @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;
|
|
|
|
/**
|
|
* @class
|
|
* @constructor
|
|
* @param {!number} x
|
|
* @param {!number} y
|
|
* @param {!number} blur
|
|
* @param {!string} color
|
|
* @returns {void}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.Shadow = function(x, y, blur, color){
|
|
/** @type {number} */
|
|
this.x = x;
|
|
/** @type {number} */
|
|
this.y = y;
|
|
/** @type {number} */
|
|
this.blur = blur;
|
|
/** @type {number} */
|
|
this.color = color;
|
|
};
|
|
|
|
/**
|
|
* @class
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.ObjectBase = class{
|
|
|
|
/**
|
|
* @param {!(string|Array.<string>)} keys
|
|
* @param {anmy|null} [_default = null]
|
|
* @returns {any|null}
|
|
* @access protected
|
|
*/
|
|
_get(keys, _default = null){
|
|
return Kanvas.get_value(keys, this.inputs, _default);
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {!Kanvas} kanvas
|
|
* @param {?Kanvas.ObjectBase} parent
|
|
* @param {!(Object.<string, any|null>|Array)} inputs
|
|
* @param {!number} [i = -1]
|
|
* @param {!(Object.<string, any|null>|Array)} [custom = {}]
|
|
* @param {!Array.<string>} [inputs_map = []]
|
|
* @access public
|
|
*/
|
|
constructor(kanvas, parent, inputs, i = -1, custom = {}, inputs_map = [
|
|
"_", "x", "y", "width", "height", "background", "childs"
|
|
]){
|
|
|
|
/** @type {Object.<string, any|null>} */
|
|
this.inputs = Kanvas.get_dictionary([(
|
|
Kanvas.is_array(inputs) ? inputs = Kanvas.map_dictionary(inputs, inputs_map) :
|
|
Kanvas.is_dictionary(inputs) ? inputs :
|
|
{}), custom], false);
|
|
|
|
// /** @type {kanvas_on_change_callback|null} */
|
|
// const on_load_event = this._get("on_load", null),
|
|
// /** @type {kanvas_on_change_callback|null} */
|
|
// on_error_event = this._get("on_error", null),
|
|
// /** @type {kanvas_on_change_callback|null} */
|
|
// on_status_change_event = this._get("on_status_change", null);
|
|
|
|
/** @type {Kanvas} */
|
|
this.kanvas = kanvas;
|
|
/** @type {Object} */
|
|
this.type = Kanvas.ObjectBase;
|
|
/** @type {string} */
|
|
this.type_name = "ObjectBase";
|
|
/** @type {boolean} */
|
|
this.full = this._get("full", false);
|
|
/** @type {boolean} */
|
|
this.percentaje = this._get("percentaje", false);
|
|
/** @type {number|null} */
|
|
this.rotation = this._get(["rotate", "rotation"], null);
|
|
/** @type {string|null} */
|
|
this.background = this._get(["background", "color"], null);
|
|
/** @type {number|null} */
|
|
this.alpha = this._get("alpha", null);
|
|
/** @type {number} */
|
|
this.status = Kanvas.UNBUILT;
|
|
// /** @type {Array.<Array.<any>|Object.<string, any|null>|Object>|null} */
|
|
// this.childs = this._get("childs", null);
|
|
/** @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 {boolean} */
|
|
this.print = this._get("print", inputs, false);
|
|
/** @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 {boolean} */
|
|
this.draw_childs = true;
|
|
/** @type {number} */
|
|
this.i = i;
|
|
/** @type {Array.<Kanvas.Shadow>} */
|
|
this.shadows = Kanvas.get_array(this._get(["shadow", "shadows"], [])).map(shadow => new Kanvas.Shadow(...shadow));
|
|
/** @type {HTMLCanvasElement|null} */
|
|
this.shadow_shape_cache = null;
|
|
/** @type {boolean} */
|
|
this.shadow_cached = false;
|
|
/** @type {boolean} */
|
|
this.is_mouse_over = false;
|
|
/** @type {Path2D|null} */
|
|
this.path = null;
|
|
/** @type {boolean} */
|
|
this.recreate_path_automatically = true;
|
|
/** @type {Kanvas.ObjectBase|null} */
|
|
this.parent = parent;
|
|
/** @type {DOMMatrix|null} */
|
|
this.matrix = null;
|
|
|
|
/** @type {Kanvas.Event} */
|
|
this.on_load = new Kanvas.Event(true);
|
|
/** @type {Kanvas.Event} */
|
|
this.on_error = new Kanvas.Event(true);
|
|
/** @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();
|
|
|
|
/** @type {number} */
|
|
this.childs_with_errors = 0;
|
|
/** @type {number} */
|
|
this.childs_loaded = 0;
|
|
/** @type {number} */
|
|
this.childs_loading = 0;
|
|
/** @type {number} */
|
|
this.childs_null = 0;
|
|
/** @type {number} */
|
|
this.childs_unbuilt = 0;
|
|
/** @type {boolean} */
|
|
this.full_loaded = !this.childs.length;
|
|
|
|
// on_load_event && this.on_load.add(on_load_event);
|
|
// on_error_event && this.on_error.add(on_error_event);
|
|
// on_status_change_event && this.on_status_change.add(on_status_change_event);
|
|
|
|
[
|
|
"load", "error", "status_change", "mouse_down", "mouse_up", "click",
|
|
"mouse_over", "mouse_out", "mouse_move"
|
|
].forEach(key => {
|
|
|
|
/** @type {kanvas_event_callback} */
|
|
const event = this._get("on_" + key);
|
|
|
|
Kanvas.is_function(event) && this["on_" + key].add(event);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
/**
|
|
* @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
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
check_click(event){
|
|
if(this.path && this.kanvas.context.isPointInPath(this.path, event.clientX, event.clientY))
|
|
this.on_click.execute(this, event);
|
|
else
|
|
this.childs.forEach(child => child.check_click(event));
|
|
};
|
|
|
|
/**
|
|
* @param {number|null} [status = null]
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
set_status(status = null){
|
|
|
|
status !== null && (this.status = status);
|
|
|
|
this.on_status_change.execute(this);
|
|
switch(this.status){
|
|
case Kanvas.ERROR:
|
|
this.on_error.autoexecute = true;
|
|
this.on_error.execute(this);
|
|
break;
|
|
case Kanvas.LOADED:
|
|
if(this.full_loaded){
|
|
this.on_load.autoexecute = true;
|
|
this.on_load.execute(this);
|
|
};
|
|
break;
|
|
default:
|
|
this.on_load.autoexecute = false;
|
|
this.on_error.autoexecute = false;
|
|
break;
|
|
};
|
|
|
|
};
|
|
|
|
/**
|
|
* @param {!Kanvas.ObjectBase} child
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
#set_child_status(child){
|
|
|
|
this.childs_loaded = 0;
|
|
this.childs_with_errors = 0;
|
|
this.childs_loading = 0;
|
|
this.childs_null = 0;
|
|
this.childs_unbuilt = 0;
|
|
|
|
this.childs.forEach(child => {
|
|
if(child === null)
|
|
this.childs_null ++;
|
|
else if(child.status !== undefined)
|
|
this.childs_unbuilt ++;
|
|
else if(child.status & Kanvas.ERROR)
|
|
this.childs_with_errors ++;
|
|
else if(child.status & Kanvas.LOADED)
|
|
this.childs_loaded ++;
|
|
else
|
|
this.childs_loading ++;
|
|
});
|
|
|
|
(this.full_loaded = this.childs.length == this.childs_null + this.childs_loaded + this.childs_with_errors) &&
|
|
self.set_status();
|
|
|
|
};
|
|
|
|
/**
|
|
* @param {number} degrees
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
rotate(degrees){
|
|
this.rotation = (this.rotation + degrees) % 360;
|
|
};
|
|
|
|
/**
|
|
* @param {!CanvasRenderingContext2D} context
|
|
* @param {!Kanvas.Shadow} shadow
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
set_shadow(context, shadow){
|
|
context.shadowOffsetX = this.kanvas._(shadow.x);
|
|
context.shadowOffsetY = this.kanvas._(shadow.y);
|
|
context.shadowBlur = this.kanvas._(shadow.blur);
|
|
context.shadowColor = shadow.color;
|
|
};
|
|
|
|
static transform_array(matrix){
|
|
return [matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f];
|
|
};
|
|
|
|
/**
|
|
* @param {!CanvasRenderingContext2D} context
|
|
* @param {!number} x
|
|
* @param {!number} y
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
predraw(context, x, y){
|
|
|
|
// if(!this.shadow_cached && this.shadows.length > 1){
|
|
|
|
// if(this.shadow_shape_cache === null){
|
|
// this.shadow_shape_cache = this.kanvas.cache.appendChild(document.createElement("canvas"));
|
|
// ["width", "height"].forEach(key => {
|
|
// this.shadow_shape_cache.setAttribute(key, this.kanvas[key]);
|
|
// });
|
|
// };
|
|
|
|
// const shape_context = this.shadow_shape_cache.getContext("2d"),
|
|
// shadows_saved = this.shadows,
|
|
// /** @type {DOMMatrix} */
|
|
// matrix = context.getTransform();
|
|
|
|
// shape_context.beginPath();
|
|
// shape_context.clearRect(0, 0, this.kanvas.width, this.kanvas.height);
|
|
// shape_context.translate(matrix.e, matrix.f);
|
|
|
|
// this.shadows = [];
|
|
// this.kanvas.shadow_cached = true;
|
|
|
|
// this.kanvas.draw(shape_context, [this]);
|
|
// shape_context.translate(-matrix.e, -matrix.f);
|
|
// shape_context.closePath();
|
|
// context.save();
|
|
// context.translate(-matrix.e, -matrix.f);
|
|
// shadows_saved.forEach(shadow => {
|
|
// context.beginPath();
|
|
// this.set_shadow(context, shadow);
|
|
// context.drawImage(this.shadow_shape_cache, 0, 0);
|
|
// context.closePath();
|
|
// });
|
|
// context.translate(matrix.e, matrix.f);
|
|
// context.restore();
|
|
|
|
// this.kanvas.shadow_cached = false;
|
|
// this.shadows = shadows_saved;
|
|
|
|
// };
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
if(this.shadows.length < 2){
|
|
if(!this.kanvas.shadow_cached && this.shadow_shape_cache){
|
|
this.shadow_shape_cache.remove();
|
|
this.shadow_shape_cache = null;
|
|
};
|
|
if(this.shadows.length == 1)
|
|
this.set_shadow(context, this.shadows[0]);
|
|
};
|
|
|
|
this.background !== null && (context.fillStyle = this.background);
|
|
if(!this.kanvas.shadow_cached){
|
|
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){
|
|
|
|
/** @type {Array.<Path2D>} */
|
|
const shape = (this.path ? [this.path] : []).map(shape => {
|
|
|
|
const real = new Path2D();
|
|
|
|
real.addPath(shape, this.matrix);
|
|
|
|
return real;
|
|
});
|
|
|
|
this.path && (this.path = shape[0]);
|
|
|
|
this.fill(context, shape);
|
|
|
|
};
|
|
|
|
// this.matrix = new DOMMatrix(context.getTransform());
|
|
context.closePath();
|
|
|
|
// this.type_name == "Position" && console.log(JSON.stringify([this.draw_childs, this.childs.length]));
|
|
if(this.draw_childs && this.childs.length){
|
|
this.path || (this.path = new Path2D());
|
|
this.kanvas.draw(context, this.childs, child => {
|
|
child && child.path && this.path.addPath(child.path);
|
|
this.#set_child_status(child);
|
|
}, this);
|
|
};
|
|
|
|
if(this.shadows.length){
|
|
|
|
// image.src = this.kanvas.canvas.toDataURL();
|
|
|
|
// context.save();
|
|
// context.clip(this.path);
|
|
// const data = context.getImageData(0, 0, this.kanvas.width, this.kanvas.height);
|
|
// context.restore();
|
|
|
|
this.shadows.forEach(shadow => {
|
|
|
|
const subcanvas = document.createElement("canvas"),
|
|
subcontext = subcanvas.getContext("2d");
|
|
|
|
subcanvas.setAttribute("width", this.kanvas.width);
|
|
subcanvas.setAttribute("height", this.kanvas.height);
|
|
subcontext.beginPath();
|
|
subcontext.styleFill = "#000";
|
|
this.set_shadow(subcontext, shadow);
|
|
subcontext.fill(this.path);
|
|
|
|
subcontext.globalCompositeOperation = "destination-out";
|
|
subcontext.beginPath();
|
|
subcontext.fillStyle = "black";
|
|
subcontext.fill(this.path);
|
|
subcontext.globalCompositeOperation = "source-over";
|
|
|
|
subcontext.clip(this.path);
|
|
subcontext.clearRect(0, 0, this.kanvas.width, this.kanvas.height);
|
|
subcontext.closePath();
|
|
|
|
context.drawImage(subcanvas, 0, 0);
|
|
|
|
subcanvas.remove();
|
|
|
|
// context.save();
|
|
// this.set_shadow(context, shadow);
|
|
// context.beginPath();
|
|
// context.fillStyle = "black";
|
|
// context.fill(this.path);
|
|
// context.restore();
|
|
// context.globalCompositeOperation = "source-atop";
|
|
// context.beginPath();
|
|
// context.fillStyle = "black";
|
|
// context.fill(this.path);
|
|
// context.closePath();
|
|
// context.fillStyle = "transparent";
|
|
// context.fill(this.path);
|
|
// context.globalCompositeOperation = "source-over";
|
|
// context.clip(this.path);
|
|
// context.clearRect(0, 0, this.kanvas.width, this.kanvas.height);
|
|
// context.transform(1, 0, 0, 1, 0, 0);
|
|
// context.closePath();
|
|
|
|
// context.save();
|
|
// this.set_shadow(context, shadow);
|
|
// context.fillStyle = "#000";
|
|
// context.fill(this.path);
|
|
// context.globalCompositeOperation = "destination-out";
|
|
// context.fill(this.path);
|
|
// context.globalCompositeOperation = "source-over";
|
|
// context.restore();
|
|
|
|
// context.save();
|
|
// this.set_shadow(context, shadow);
|
|
// context.fillStyle = "#000";
|
|
// context.fill(this.path);
|
|
// context.restore();
|
|
|
|
// context.save();
|
|
// context.clip(this.path);
|
|
// context.clearRect(0, 0, this.kanvas.width, this.kanvas.height);
|
|
// context.restore();
|
|
|
|
});
|
|
|
|
// context.save();
|
|
// context.clip(this.path);
|
|
// context.drawImage(image, 0, 0);
|
|
// context.restore();
|
|
|
|
// context.putImageData(data, 0, 0);
|
|
|
|
};
|
|
|
|
// context.translate(-x, -y);
|
|
context.restore();
|
|
|
|
};
|
|
|
|
/**
|
|
* @param {!CanvasRenderingContext2D} context
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
draw_level(context){
|
|
if(this.status == Kanvas.LOADED){
|
|
|
|
/** @type {number} */
|
|
const x = this.kanvas._(this.x),
|
|
/** @type {number} */
|
|
y = this.kanvas._(this.y);
|
|
|
|
this.recreate_path_automatically && (this.path = new Path2D());
|
|
|
|
this.predraw(context, x, y);
|
|
this.draw(context, x, y);
|
|
this.postdraw(context, x, y);
|
|
|
|
};
|
|
};
|
|
|
|
};
|
|
|
|
/** @type {Object.<string, Kanvas.ObjectBase>} */
|
|
Kanvas.SHAPES = {
|
|
|
|
/**
|
|
* @class
|
|
* @extends Kanvas.ObjectBase
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Rectangle : class extends Kanvas.ObjectBase{
|
|
|
|
/**
|
|
* @param {!Kanvas} kanvas
|
|
* @param {!(Object.<string, any|null>|Array)} inputs
|
|
* @param {!number} [i = -1]
|
|
* @access public
|
|
*/
|
|
constructor(kanvas, parent, inputs, i = -1){
|
|
super(kanvas, parent, inputs, i);
|
|
|
|
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 {!(Object.<string, any|null>|Array)} inputs
|
|
* @param {!number} [i = -1]
|
|
* @access public
|
|
*/
|
|
constructor(kanvas, parent, inputs, i = -1){
|
|
super(kanvas, parent, inputs, i, {}, ["_", "x", "y", "radius", "from", "to"]);
|
|
|
|
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 {!(Object.<string, any|null>|Array)} inputs
|
|
* @param {!number} [i = -1]
|
|
* @access public
|
|
*/
|
|
constructor(kanvas, parent, inputs, i = -1){
|
|
super(kanvas, parent, inputs, i, {}, [
|
|
"_", "x", "y", "text", "size", "font", "color", "align", "baseline", "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 {!(Object.<string, any|null>|Array)} inputs
|
|
* @param {!number} [i = -1]
|
|
* @access public
|
|
*/
|
|
constructor(kanvas, parent, inputs, i = -1){
|
|
super(kanvas, parent, inputs, i, {}, ["_", "source", "x", "y"].concat(...(Kanvas.is_array(inputs) ? [
|
|
inputs.length > 4 ? ["width", "height"] : [],
|
|
inputs.length > 6 ? ["sub_x", "sub_y", "sub_width", "sub_height"] : []
|
|
] : []), ["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
|
|
*/
|
|
Cache : class extends Kanvas.ObjectBase{
|
|
|
|
/**
|
|
* @param {!Kanvas} kanvas
|
|
* @param {!(Object.<string, any|null>|Array)} inputs
|
|
* @param {!number} [i = -1]
|
|
* @access public
|
|
*/
|
|
constructor(kanvas, parent, inputs, i = -1){
|
|
super(kanvas, parent, inputs, i, {}, [
|
|
"_", "x", "y", "width", "height", "childs"
|
|
]);
|
|
|
|
this.type = Kanvas.SHAPES.Cache;
|
|
this.type_name = "Cache";
|
|
this.draw_childs = false;
|
|
this.full_loaded = true;
|
|
this.recreate_path_automatically = false;
|
|
this.status = Kanvas.LOADED;
|
|
|
|
/** @type {boolean} */
|
|
this.built = false;
|
|
/** @type {Image|null} */
|
|
this.cache = null;
|
|
|
|
};
|
|
|
|
predraw(context, x, y){
|
|
super.predraw(context, x, y);
|
|
if(!this.built){
|
|
this.build();
|
|
this.built = true;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
build(){
|
|
|
|
this.set_status(Kanvas.LOADING);
|
|
|
|
/** @type {HTMLCanvasElement} */
|
|
const canvas = document.createElement("canvas"),
|
|
/** @type {CanvasRenderingContext2D} */
|
|
context = canvas.getContext("2d"),
|
|
width = this.kanvas._(this.width),
|
|
height = this.kanvas._(this.height);
|
|
/** @type {number} */
|
|
let loading = this.childs.reduce((total, child) => total + (child ? 1 : 0), 0),
|
|
/** @type {number} */
|
|
done = 0,
|
|
/** @type {boolean} */
|
|
synchronous = true;
|
|
|
|
this.path = new Path2D();
|
|
|
|
const end = () => {
|
|
if(++ done != loading)
|
|
return;
|
|
|
|
if(!synchronous){
|
|
this.path = new Path2D();
|
|
context.clearRect(0, 0, width, height);
|
|
console.log(this.childs);
|
|
this.kanvas.draw(context, this.childs, child => {
|
|
child && this.path.addPath(child.path);
|
|
}, {
|
|
matrix : new DOMMatrix([1, 0, 0, 1, 0, 0])
|
|
});
|
|
};
|
|
|
|
/** @type {string} */
|
|
const data = canvas.toDataURL("image/webp", 1);
|
|
|
|
Kanvas.load_image(data, (image, ok) => {
|
|
this.cache = image;
|
|
this.set_status(ok ? Kanvas.LOADED : Kanvas.ERROR);
|
|
// console.log([this.status, this.full_loaded, this.childs.length, this.childs_null, this.childs_loaded, this.childs_with_errors, this]);
|
|
});
|
|
canvas.remove();
|
|
|
|
};
|
|
|
|
canvas.setAttribute("width", width);
|
|
canvas.setAttribute("height", height);
|
|
|
|
this.kanvas.draw(context, this.childs, child => {
|
|
console.log(child);
|
|
if(child){
|
|
this.path.addPath(child.path);
|
|
child.on_load.add(end);
|
|
}else
|
|
end();
|
|
}, {
|
|
matrix : new DOMMatrix([1, 0, 0, 1, 0, 0])
|
|
});
|
|
|
|
synchronous = false;
|
|
|
|
};
|
|
|
|
/**
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
draw(context, x, y){
|
|
if(this.cache){
|
|
this.path.rect(0, 0, this.cache.width, this.cache.height);
|
|
context.save();
|
|
context.setTransform(...Kanvas.ObjectBase.transform_array(this.matrix));
|
|
context.drawImage(this.cache, 0, 0);
|
|
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 {!(Object.<string, any|null>|Array)} inputs
|
|
* @param {!number} [i = -1]
|
|
* @access public
|
|
*/
|
|
constructor(kanvas, parent, inputs, i = -1){
|
|
super(kanvas, parent, inputs, i, {}, ["_", "x", "y", "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 {!(Object.<string, any|null>|Array)} inputs
|
|
* @param {!number} [i = -1]
|
|
* @access public
|
|
*/
|
|
constructor(kanvas, parent, inputs, i = -1){
|
|
super(kanvas, parent, inputs, i, {}, ["_", "x", "y", "shape"]);
|
|
|
|
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 {!(Object.<string, any|null>|Array)} inputs
|
|
* @param {!number} [i = -1]
|
|
* @access public
|
|
*/
|
|
constructor(kanvas, parent, inputs, i = -1){
|
|
super(kanvas, parent, inputs, i, {}, ["_", "x", "y", "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
|
|
* @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 && !ended; 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
|
|
]));
|
|
length && ++ k == length && (ended = true);
|
|
};
|
|
|
|
}else
|
|
processed[key].push(sprite instanceof Kanvas.SpritesMap ? sprite : new Kanvas.SpritesMap(sprite));
|
|
});
|
|
};
|
|
|
|
return processed;
|
|
};
|
|
|
|
return SpritesMap;
|
|
})();
|
|
|
|
/**
|
|
* @class
|
|
* @constructor
|
|
* @returns {void}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.Event = function(unique = false){
|
|
|
|
/** @type {Kanvas.Event} */
|
|
const self = this,
|
|
/** @type {Array.<kanvas_event_callback>} */
|
|
events = [];
|
|
|
|
/** @type {boolean} */
|
|
this.autoexecute = false;
|
|
/** @type {number} */
|
|
this.length = 0;
|
|
/** @type {boolean} */
|
|
this.unique = unique;
|
|
|
|
/**
|
|
* @param {...any} [parameters]
|
|
* @returns {Array.<any|null>}
|
|
* @access public
|
|
*/
|
|
this.execute = (...parameters) => events.map((event, i) => {
|
|
|
|
let response = null;
|
|
|
|
if(event){
|
|
response = event(...parameters, i);
|
|
self.unique && (events[i] = null);
|
|
};
|
|
|
|
return response;
|
|
});
|
|
|
|
/**
|
|
* @param {!kanvas_event_callback} callback
|
|
* @returns {number|null}
|
|
* @access public
|
|
*/
|
|
this.add = callback => {
|
|
|
|
/** @type {number|null} */
|
|
let i = null;
|
|
|
|
if(Kanvas.is_function(callback)){
|
|
|
|
/** @type {number} */
|
|
const l = events.length;
|
|
|
|
for(i = 0; i < l; i ++)
|
|
if(!events[i])
|
|
break;
|
|
|
|
this.length ++;
|
|
events[i] = callback;
|
|
|
|
self.autoexecute && callback();
|
|
|
|
};
|
|
|
|
return i;
|
|
};
|
|
|
|
/**
|
|
* @param {!number} i
|
|
* @returns {boolean}
|
|
* @access public
|
|
*/
|
|
this.remove = i => {
|
|
if(Kanvas.is_index(i) && events[i]){
|
|
events[i] = null;
|
|
this.length --;
|
|
return true;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
};
|
|
|
|
/**
|
|
* @class
|
|
* @constructor
|
|
* @param {!number} x
|
|
* @param {!number} y
|
|
* @param {!number} radius
|
|
* @returns {void}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.Circle = function(i, x, y, radius){
|
|
|
|
/** @type {Kanvas.Circle} */
|
|
const self = this;
|
|
|
|
/** @type {number} */
|
|
this.i = i;
|
|
/** @type {string} */
|
|
this.type = "circle";
|
|
/** @type {number} */
|
|
this.x = x;
|
|
/** @type {number} */
|
|
this.y = y;
|
|
/** @type {number} */
|
|
this.radius = radius;
|
|
|
|
/**
|
|
* @param {!number} x
|
|
* @param {!number} y
|
|
* @returns {boolean}
|
|
* @access public
|
|
*/
|
|
this.check = (x, y) => (
|
|
(x - self.x) ** 2 +
|
|
(y - self.y) ** 2
|
|
) ** .5 <= self.radius;
|
|
|
|
};
|
|
|
|
/**
|
|
* @class
|
|
* @constructor
|
|
* @param {!number} x
|
|
* @param {!number} y
|
|
* @param {!number} width
|
|
* @param {!number} height
|
|
* @returns {void}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.Rectangle = function(i, x, y, width, height){
|
|
|
|
/** @type {Kanvas.Rectangle} */
|
|
const self = this;
|
|
|
|
/** @type {number} */
|
|
this.i = i;
|
|
/** @type {string} */
|
|
this.type = "rectangle";
|
|
/** @type {number} */
|
|
this.x = x;
|
|
/** @type {number} */
|
|
this.y = y;
|
|
/** @type {number} */
|
|
this.width = width;
|
|
/** @type {number} */
|
|
this.height = height;
|
|
|
|
/**
|
|
* @param {!number} x
|
|
* @param {!number} y
|
|
* @returns {boolean}
|
|
* @access public
|
|
*/
|
|
this.check = (x, y) => (
|
|
x >= self.x && x <= self.x + self.width &&
|
|
y >= self.y && y <= self.y + self.height
|
|
);
|
|
|
|
};
|
|
|
|
/**
|
|
* @class
|
|
* @constructor
|
|
* @returns {void}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.EventMouse = function(){
|
|
|
|
/** @type {Kanvas.Event} */
|
|
const event = new Kanvas.Event(),
|
|
/** @type {Array.<Kanvas.Circle|Kanvas.Rectangle|null>} */
|
|
properties = [];
|
|
|
|
/**
|
|
* @param {...any} [parameters]
|
|
* @returns {Array.<any|null>}
|
|
* @access public
|
|
*/
|
|
this.execute = event.execute;
|
|
|
|
/**
|
|
* @param {!kanvas_event_mouse_callback} callback
|
|
* @param {!number} x
|
|
* @param {!number} y
|
|
* @param {...any} size
|
|
* @returns {number|null}
|
|
* @access public
|
|
*/
|
|
this.add = (callback, x, y, ...size) => {
|
|
|
|
/** @type {number|null} */
|
|
const i = event.add((x, y, ...parameters) => {
|
|
properties[i].check(x, y) && callback(x, y, ...parameters);
|
|
});
|
|
|
|
i !== null && (properties[i] = new Kanvas[
|
|
size.length == 1 ? "Circle" :
|
|
"Rectangle"](i, x, y, ...size));
|
|
|
|
return properties[i];
|
|
};
|
|
|
|
/**
|
|
* @param {!(Kanvas.Rectangle|Kanvas.Circle)} property
|
|
* @returns {boolean}
|
|
* @access public
|
|
*/
|
|
this.remove = property => {
|
|
if(property && Kanvas.number(property.i) && event.remove(property.i)){
|
|
properties[property.i] = null;
|
|
return true;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
};
|
|
|
|
/**
|
|
* @constructor
|
|
* @returns {void}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.Screen = function(){
|
|
/** @type {number} */
|
|
this.x = 0;
|
|
/** @type {number} */
|
|
this.y = 0;
|
|
};
|
|
|
|
/**
|
|
* @param {any|null} value
|
|
* @returns {boolean}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.is_array = value => value instanceof Array;
|
|
|
|
/**
|
|
* @param {any|null} value
|
|
* @returns {boolean}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.is_dictionary = value => value && value.constructor == Object;
|
|
|
|
/**
|
|
* @param {any|null} value
|
|
* @returns {boolean}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.is_string = value => typeof value == "string";
|
|
|
|
/**
|
|
* @param {any|null} value
|
|
* @returns {boolean}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.is_function = value => typeof value == "function";
|
|
|
|
/**
|
|
* @param {!(string|Array.<string>)} value
|
|
* @returns {Array.<string>}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.get_keys = keys => (Kanvas.is_array(keys) ? keys : [keys]).filter((key, i, keys) => Kanvas.is_string(key) && keys.indexOf(key) == i);
|
|
|
|
/**
|
|
* @param {!(string|Array.<string>)} value
|
|
* @param {!(Object.<string, any|null>|Array<any|null>)} inputs
|
|
* @param {any|null|undefined} [_default = undefined]
|
|
* @returns {any|null|undefined}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.get_value = (keys, inputs, _default = undefined) => {
|
|
|
|
/** @type {any|null|undefined} */
|
|
let response;
|
|
|
|
Kanvas.is_array(keys) || (keys = Kanvas.get_keys(keys));
|
|
|
|
return (
|
|
Kanvas.is_dictionary(inputs) ? keys.some(key => (response = inputs[key]) !== undefined) :
|
|
Kanvas.is_array(inputs) ? inputs.some(subinputs => (response = Kanvas.get_value(keys, subinputs)) !== undefined) :
|
|
false) ? response : _default;
|
|
};
|
|
|
|
/**
|
|
* @param {any|null} item
|
|
* @returns {boolean}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.is_class = item => typeof item == "function" && item.toString().startsWith("class ");
|
|
|
|
/**
|
|
* @param {any|null} item
|
|
* @returns {boolean}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.is_html_object = item => item && (item.tagName || item.nodeName);
|
|
|
|
/**
|
|
* @param {any|null} item
|
|
* @returns {boolean}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.is_number = item => typeof item == "number";
|
|
|
|
/**
|
|
* @param {any|null} item
|
|
* @returns {boolean}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.is_integer = item => Kanvas.is_number(item) && item == item >> 0;
|
|
|
|
/**
|
|
* @param {any|null} item
|
|
* @returns {boolean}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.is_index = item => Kanvas.is_integer(item) && item >= 0;
|
|
|
|
/**
|
|
* @param {any|null|Array} item
|
|
* @returns {Array.<any|null>}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.get_array = item => Kanvas.is_array(item) ? item : [item];
|
|
|
|
/**
|
|
* @param {!(Object.<string, any|null>|Array)} dictionaries
|
|
* @param {!boolean} [overwrite = false]
|
|
* @param {!Object.<string, any|null>} [dictionary = {}]
|
|
* @returns {Object.<string, any|null>}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.get_dictionary = (dictionaries, overwrite = false, dictionary = {}) => {
|
|
|
|
Kanvas.get_array(dictionaries).forEach(value => {
|
|
if(Kanvas.is_array(value))
|
|
Kanvas.get_dictionary(value, overwrite, dictionary);
|
|
else if(Kanvas.is_dictionary(value)){
|
|
for(const key in value)
|
|
(overwrite || dictionary[key] === undefined) && (dictionary[key] = value[key]);
|
|
};
|
|
});
|
|
|
|
return dictionary;
|
|
};
|
|
|
|
/**
|
|
* @param {!Array.<any|null>} array
|
|
* @param {!Array.<string>} map
|
|
* @returns {Object.<string, any|null>}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.map_dictionary = (array, map) => map.reduce((dictionary, key, i) => {
|
|
|
|
dictionary[key] = array[i];
|
|
|
|
return dictionary;
|
|
}, {});
|
|
|
|
/**
|
|
* @param {any|null} value
|
|
* @param {!Array.<any|null>} nested_array
|
|
* @param {number|null} [_default = null]
|
|
* @returns {number|null}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.get_i_from_nested_array = (value, nested_array, _default = null) => {
|
|
|
|
/** @type {number} */
|
|
const l = nested_array.length;
|
|
|
|
for(let i = 0; i < l; i ++)
|
|
if(nested_array[i].includes(value))
|
|
return i;
|
|
return _default;
|
|
};
|
|
|
|
/**
|
|
* @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 {!number} degrees
|
|
* @returns {number}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.to_radians = degrees => 2 * Math.PI * degrees / 360;
|
|
|
|
/**
|
|
* @param {!kanvas_execute_callback} method
|
|
* @param {...any} parameters
|
|
* @returns {any|null}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.execute = (method, ...parameters) => Kanvas.is_function(method) && method(...parameters);
|
|
|
|
/**
|
|
* @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 {any|null} item
|
|
* @returns {boolean}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
Kanvas.is_image = item => item instanceof Image;
|
|
|
|
return Kanvas;
|
|
})(); |