"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.|Array.>} inputs * @returns {void} * @access public */ export const Kanvas = (function(){ /** * @constructs Kanvas * @param {Object.|Array.>} custom * @returns {void} * @access public */ const Kanvas = function(custom){ /** @type {Kanvas} */ const self = this, /** @type {Object.} */ default_settings = {}, /** @type {Array.} */ threads = [], /** @type {Array.} */ thread_times = [], /** @type {Kanvas.Screen} */ screen = new Kanvas.Screen(), /** @type {Object.} */ 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.} */ 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.)} keys * @param {?(Object.|Array.)} [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.)} 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.|Object.|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.)} 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.)} 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.|Array)} inputs * @param {!number} [i = -1] * @param {!(Object.|Array)} [custom = {}] * @param {!Array.} [inputs_map = []] * @access public */ constructor(kanvas, parent, inputs, i = -1, custom = {}, inputs_map = [ "_", "x", "y", "width", "height", "background", "childs" ]){ /** @type {Object.} */ 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.|Object.|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.|Object.} */ 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.} */ 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.} [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.} */ 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.} */ Kanvas.SHAPES = { /** * @class * @extends Kanvas.ObjectBase * @access public * @static */ Rectangle : class extends Kanvas.ObjectBase{ /** * @param {!Kanvas} kanvas * @param {!(Object.|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.} */ 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.|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.} */ 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.|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.|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.|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.|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.} */ static methods = [null, null, "line", null, "quadraticCurve", "arc", "bezierCurve"]; /** * @param {!Kanvas} kanvas * @param {!(Object.|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.>} */ 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.|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.} */ this.sources = Kanvas.get_array(this._get(["sources", "source", "src", "links", "urls", "paths", "link", "url", "path"], [])); /** @type {Object.} */ 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.>} */ 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.|Object.)} inputs * @returns {void} * @access public * @static */ Kanvas.SpritesMap = (function(){ /** * @constructs Kanvas.SpritesMap * @param {!(string|number|Array.|Object.)} 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.|Kanvas.SpritesMap>>} map * @param {!kanvas_source_callback} [source_callback] * @returns {Object.>} * @access public * @static */ SpritesMap.process = (map, source_callback) => { /** @type {Object.>} */ 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.} */ events = []; /** @type {boolean} */ this.autoexecute = false; /** @type {number} */ this.length = 0; /** @type {boolean} */ this.unique = unique; /** * @param {...any} [parameters] * @returns {Array.} * @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.} */ properties = []; /** * @param {...any} [parameters] * @returns {Array.} * @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.)} value * @returns {Array.} * @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.)} value * @param {!(Object.|Array)} 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.} * @access public * @static */ Kanvas.get_array = item => Kanvas.is_array(item) ? item : [item]; /** * @param {!(Object.|Array)} dictionaries * @param {!boolean} [overwrite = false] * @param {!Object.} [dictionary = {}] * @returns {Object.} * @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.} array * @param {!Array.} map * @returns {Object.} * @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.} 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.} 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; })();