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