Kanvas = function(input){ const self = this, default_settings = { quality : 1, quality_x : 1, quality_y : 1, cells : 100, origin : 5, // Posición origen. Mirar teclado numérico para ver los diferentes valores para cada posición. frames_per_second : 60, ratio : null, // Expone la proporción de tamaño del Canvas (16/9 para pantallas WideScreen por ejemplo). Si es equivalente a falso cubrirá todo el área de la capa donde se encuentre. overwrite : false, position : "body", autostart : true, object_name : "kanvas", class : "kanvas", application : "Kanvas", x : 0, y : 0, width : 0, height : 0, color : "#000", blur : 0, italic : false, bold : false, size : 1, font : "Arial", align : "left", alpha : 1, degrees : 0, baseline : "Alphabetic", shadow_x : 0, shadow_y : 0, shadow_color : "#000", shadow_blur : 0, border_color : "#000", border_width : 0, text_italic : false, text_bold : false, text_size : 1, font_family : "Arial", text_color : "#000", text_align : "left", text_alpha : 1, rotate_x : 0, rotate_y : 0, rotate_degrees : 0, image_x : 0, image_y : 0, image_alpha : 1, rectangle_color : "#000", rectangle_alpha : 1, rectangle_x : 0, rectangle_y : 0, text_baseline : "Alphabetic", default_value : null, hash_alphabet : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", hash_length : 7 }, cache = [], _screen = {x : 0, y : 0}, position = {x : 0, y : 0}, events = {}, threads = [], hashes = [], attributes = { natives : ["id", "class", "onmousemove", "tabindex", "onkeydown", "onkeyup"] }; let q, qx, qy, // Calidad porcentual. c, // Número de celdas en el lado más corto de la pantalla o área de trabajo rectangular. fx, fy, // Posición contra el foco. dx, dy, // Distancia desde los laterales hasta el cuadrado de trabajo. s, // Tamaño de la celda. Solo se mide un lado pues será cuadrada. mx, my, // Posición origen del lienzo. thread = null, timeout = 0, last_time = 0, started = false, cache_container, context, canvas, cache_l = 0, thread_object = null; let item_self = this.item_self; let hash_self = this.hash_self; let object_name = this.object_name; let mouse = this.mouse = {x : 0, y : 0}; this.map = []; const null_or_undefined = this.null_or_undefined = value => value === undefined || value === null; const allow_nulls = this.allow_nulls = nulls => typeof nulls == "boolean" ? nulls : settings(["nulls", "allow_nulls"], null, false, false); const default_value = this.default_value = (_default, nulls) => _default !== undefined && (nulls || _default !== null) ? _default : settings(["default_value", "default", "by_default"], null, null, true); const settings = this.settings = (names, inputs, _default, nulls) => { if(!names) return default_value(_default, nulls); nulls = allow_nulls(nulls); const l = (names.push ? names : names = [names]).length, m = (inputs = (inputs ? inputs.push ? inputs : [inputs] : []).concat([input, default_settings])).length; for(let j = 0; j < m; j ++) if(typeof inputs[j] == "object") for(let i = 0; i < l; i ++) if(names[i] && inputs[j][names[i]] !== undefined && (nulls || inputs[j][names[i]] !== null)) return inputs[j][names[i]]; return default_value(_default, nulls); }; const threads_function = () => threads.forEach(thread => thread && thread()); const threads_start = this.threads_start = frames_per_second => thread_object === null && (thread_object = setInterval(threads_function, 1000 / (isNaN(frames_per_second) || frames_per_second < 1 ? settings(["frames_per_second", "fps"]) : frames_per_second))); const threads_stop = this.threads_stop = () => { if(thread_object === null) return; clearInterval(thread_object); thread_object = null; }; const threads_add = this.threads_add = callback => { if(typeof callback != "function") return null; let i = 0; const l = threads.length; for(; i < l; i ++) if(!threads[i]) break; threads[i] = callback; return i; }; const threads_remove = this.threads_remove = i => !isNaN(i) && threads[i] && (threads[i] = null); const is_html_object = this.is_html_object = variable => typeof variable == "object" && (variable.tagName || variable.nodeName); const preload = this.preload = (selector, callback) => { if(typeof callback != "function") return; if(!selector){ callback(null, false, "NO_SELECTOR"); return; }; if(is_html_object(selector)){ callback(selector, false, "OK"); return; }; if(!selector.substr){ callback(null, false, "BAD_TYPE"); return; }; let item; try{ if(item = document.querySelector(selector)){ callback(item, false, "OK"); return; }; }catch(exception){ callback(null, false, "BAD_SELECTOR"); return; }; const timeout = settings(["preload_timeout", "timeout"]), date = Date.now(); let preload = threads_add(() => { if(item = document.querySelector(selector)){ threads_remove(preload); callback(item, true, "OK"); }else if(Date.now() - date > timeout){ threads_remove(preload); callback(null, true, "TIMEOUT"); }; }); }; const hash = this.hash = () => { let hash, alphabet = settings(["hash_alphabet", "alphabet"]); const length = settings(["hash_length", "length"]), l = (alphabet.push ? alphabet : alphabet = alphabet.split("")).length; do{ hash = ""; while((hash += alphabet[Math.random() * l >> 0]).length < length); }while( hashes.includes(hash) || /^\d/.test(hash) || document.querySelector("." + hash + ",#" + hash + ",[name=" + hash + "]") ); hashes.push(hash); return hash; }; const set_attribute = this.set_attribute = (item, custom_attributes) => { if(!is_html_object(item) || typeof custom_attributes != "object") return; for(const name in custom_attributes) item.setAttribute((attributes.natives.includes(name) ? "" : "data-") + name.replace(/[^a-z\d-]+/g, "-"), custom_attributes[name]); }; const construct = () => { const custom_attributes = { natives : settings("attributes_natives") }; for(const key in custom_attributes){ !attributes[key] && (attributes[key] = []); (custom_attributes ? custom_attributes.push ? custom_attributes : [custom_attributes] : []).forEach(new_attribute => attributes[key].push(new_attribute)); }; object_name = self.object_name = settings("object_name"); settings("autostart") && self.start(); }; const range_analyze = range => !range || ( mouse.x >= range.x && mouse.x <= range.x + range.width && mouse.y >= range.y && mouse.y <= range.y + range.height ); const on_resize_method = screen => { if(_screen.x == item_self.offsetWidth && _screen.y == item_self.offsetHeight) return; const width = canvas.width = (_screen.x = item_self.offsetWidth) * q * qx, height = canvas.height = (_screen.y = item_self.offsetHeight) * q * qy; if(width < height){ s = width / c; dx = 0; dy = -(c - (c * height / width)) / 2; mx = position.x; my = position.y + dy; }else{ s = height / c; dx = -(c - (c * width / height)) / 2; dy = 0; mx = position.x + dx; my = position.y; }; //resize_methods.forEach((method) => {if(method)method();}); execute_event("resize"); }; const position_set = () => { const origin = settings("origin"); position.x = c * (.5 * ((origin - 1) % 3)); position.y = c * (1 - (.5 * ((origin - 1) / 3 >> 0))); }; this.start = () => { if(started) return; started = true; q = settings(["quality", "q"]); qx = settings(["quality_x", "qx"]); qy = settings(["quality_y", "qy"]); timeout = 1000 / settings("frames_per_second"); c = settings("cells"); position_set(); threads_start(); preload(settings("position"), (position, asynchronous, error) => { if(!position){ console.error("ERROR. Position HTML for install GUI CANVAS is bad. [" + error + "]"); return; }; const _class = (hash_self = self.hash_self = hash()) + " " + settings("class"); !new RegExp("\\b" + default_settings.class + "\\b").test(_class) && (_class += " " + default_settings.class); set_attribute(item_self = self.item_self = position.appendChild(document.createElement("div")), { id : hash_self, hash : hash_self, class : _class, application : default_settings.application, onmousemove : object_name + ".check_mouse(this, event);", tabindex : 0, onkeydown : object_name + ".key_down(this, event);", onkeyup : object_name + ".key_up(this, event);" }); set_attribute(cache_container = item_self.appendChild(document.createElement("div")), { class : "cache" }); set_attribute(canvas = item_self.appendChild(document.createElement("canvas")), { class : "canvas" }); context = canvas.getContext("2d"); thread = threads_add(thread_method); on_resize_thread = threads_add(on_resize_method); item_self.onclick = () => execute_event("click"); }); }; // o = Origin {mx, my} const draw = this.draw = (map, context, o) => map && map.forEach((level, i) => level && (level.push && draw(level, context, o || {mx : mx, my : my}) || (level.type && components[level.type] && components[level.type](level, context, self, o || {mx : mx, my : my})))); const refresh_draw = () => { if(!context) return; context.clearRect(0, 0, canvas.width, canvas.height); draw(self.map, context); }; const thread_method = () => { const date = Date.now(); if(date - last_time < timeout) return; last_time = date; refresh_draw(); }; // Establecer posición sobre el foco de origen. // Establecer posición sobre los laterales del recuadro o área de trabajo. const value = this.value = (value, quality) => q * value * (quality || 1); // const _x = this._x = value => q * qx * value * s; // const _y = this._y = value => q * qy * value * s; const _x = this._x = value => value * s; const _y = this._y = value => value * s; this.cache_set = item => { let i = 0; for(; i < cache_l; i ++) if(cache[i] === null) break; cache[i] = item; cache_l = cache.length; return i; }; const get_new_cache_i = item => item.cache_i = self.cache_set(true); this.cache_clean = i => !isNaN(i) && i >= 0 && i < cache.length && cache[i] !== null && (cache[i] = null); const preload_cache_items = (items, callback_per_item, callback, i) => { if(i >= items.length){ typeof callback == "function" && callback(); return; }; const end = () => { typeof callback_per_item == "function" && callback_per_item(i, items[i]); preload_cache_items(items, callback_per_item, callback, i + 1); }; if(!items[i]){ end(); return; }; switch(items[i].type){ case "image": const source = items[i].source || items[i].value; if(!source){ end(); return; }; const image = new Image(); image.src = source; image.crossOrigin = "anonymous"; image.onload = () => { items[i].cache_i = self.cache_set(image); // console.log([items[i], items[i].source || items[i].value, cache[items[i].cache_i]]); end(); }; image.onerror = end; break; default: items[i].cache_i = self.cache_set(image); end(); break; }; }; this.preload_cache_items = (items, callback_per_item, callback) => { if(!items){ typeof callback == "function" && callback(); return; }; !items.pus && (items = [items]); preload_cache_items(items, callback_per_item, callback, 0); }; const angle = this.angle = (x, y, randians) => { if(typeof x == "object"){ const line = x.push ? { x : x[0] - y[0], y : x[1] - y[1] } : { x : x.x - y.x, y : x.y - y.y }; x = line.x; y = line.y; }; let angle = Math.asin(y / ((x ** 2) + (y ** 2)) ** .5); // if(x >= 0) // angle += Math.PI / 2; // else // angle = (1.5 * Math.PI) - angle; return (x >= 0 ? angle + (Math.PI / 2) : ((1.5 * Math.PI) - angle)) * (randians ? 1 : 180 / Math.PI); }; const shadow = (data, context) => { const z = dx < dy ? _x : _y; if(!data.ok){ isNaN(data.x) && (data.x = settings(["shadow_x", "x"])); isNaN(data.y) && (data.y = settings(["shadow_y", "y"])); !data.color && (data.color = settings(["shadow_color", "color"])); isNaN(data.blur) && (data.blur = settings(["shadow_blur", "blur"])); data.ok = true; }; context.shadowOffsetX = z(data.x); context.shadowOffsetY = z(data.y); context.shadowColor = data.color; context.shadowBlur = z(data.blur); }; const border = (data, context) => { if(!data.ok){ !data.color && (data.color = settings(["border_color", "color"])); isNaN(data.width) && (data.width = settings(["border_width", "width"])); data.ok = true; }; context.strokeStyle = data.color; context.lineWidth = (dx < dy ? _x : _y)(data.width); }; const components = { rotate : (data, context, kanvas, o) => { if(data.ignore) return; if(!data.ok){ isNaN(data.x) && (data.x = settings(["rotate_x", "x"])); isNaN(data.y) && (data.y = settings(["rotate_y", "y"])); isNaN(data.degrees) && (data.degrees = settings(["rotate_degrees", "degrees"])); data.ok = true; }; // console.log(JSON.stringify(data)); // console.log(JSON.stringify([_x(data.x + mx), _y(data.y + my)])); context.save(); context.translate(_x(data.x + o.mx), _y(data.y + o.my)); context.rotate(data.degrees * Math.PI / 180); draw(data.childs, context, {mx : 0, my : 0}); context.restore(); }, image : (data, context, kanvas, o) => { if(data.ignore) return; const draw_image = () => { if(!data.ok){ isNaN(data.swidth) && (data.swidth = cache[data.cache_i].width); isNaN(data.sheight) && (data.sheight = cache[data.cache_i].height); isNaN(data.width) && (data.width = data.swidth); isNaN(data.height) && (data.height = data.sheight); isNaN(data.x) && (data.x = settings(["image_x", "x"])); isNaN(data.y) && (data.y = settings(["image_y", "y"])); isNaN(data.alpha) && (data.alpha = settings(["image_alpha", "alpha"])); isNaN(data.sx) && (data.sx = 0); isNaN(data.sy) && (data.sy = 0); isNaN(data.mx) && (data.mx = 0); isNaN(data.my) && (data.my = 0); data.ok = true; }; const half_width = data.width / 2, half_height = data.height / 2; context.save(); context.globalAlpha = data.alpha; context.translate(_x(o.mx + data.x + data.mx + half_width), _y(o.my + data.y + data.my + half_height)); context.rotate(data.rotate * Math.PI / 180); context.drawImage( cache[data.cache_i], data.sx, data.sy, data.swidth, data.sheight, _x(-half_width), _y(-half_height), _x(data.width), _y(data.height) ); draw(data.childs, context, {mx : 0, my : 0}); context.restore(); }; if(isNaN(data.cache_i)){ const i = get_new_cache_i(data); cache[i] = new Image(); cache[i].src = data.source || data.image; cache[i].crossOrigin = "anonymous"; cache[i].onload = draw_image; }else draw_image(); }, cache : (data, context, kanvas, o) => { if(data.ignore) return; context.save(); if(isNaN(data.cache_i)){ const cache_canvas = cache_container.appendChild(document.createElement("canvas")), cache_context = cache_canvas.getContext("2d"), width = data.width || canvas.width, height = data.height || canvas.height; image = new Image(); cache_canvas.width = width; cache_canvas.height = height; get_new_cache_i(data); cache_context.save(); // cache_context.translate(_x(-o.mx), _y(-o.my)); draw(data.childs, cache_context, {mx : 0, my : 0}); image.src = cache_canvas.toDataURL("image/png"); cache[data.cache_i] = image; cache_context.restore(); cache_canvas.remove(); }; context.drawImage(cache[data.cache_i], 0, 0); context.restore(); }, rectangle : (data, context, kanvas, o) => { if(data.ignore)return; const proportion = canvas.width > canvas.height ? canvas.width / canvas.height : canvas.height / canvas.width; if(!data.ok){ isNaN(data.alpha) && (data.alpha = settings(["rectangle_alpha", "alpha"])); !data.color && (data.color = settings(["rectangle_color", "color"])); isNaN(data.x) && (data.x = settings(["rectangle_x", "x"])); isNaN(data.y) && (data.y = settings(["rectangle_y", "y"])); isNaN(data.width) && (data.width = proportion * c); isNaN(data.height) && (data.height = proportion * c); isNaN(data.mx) && (data.mx = 0); isNaN(data.my) && (data.my = 0); data.ok = true; }; context.save(); data.shadow && (shadow(data.shadow, context)); context.globalAlpha = data.alpha; context.fillStyle = data.color; context.translate(_x(o.mx + data.x + data.mx), _y(o.my + data.y + data.my)); context.rotate(data.rotate * Math.PI / 180); context.translate(_x(-data.mx), _y(-data.my)); context.fillRect(_x(data.x), _y(data.y), _x(data.width), _y(data.height)); draw(data.childs, context, {mx : 0, my : 0}); context.restore(); }, text : (data, context, kanvas, o) => { if(data.ignore) return; if(!data.ok){ typeof data.italic != "boolean" && (data.italic = settings(["text_italic", "italic"])); isNaN(data.bold) && typeof data.bold != "boolean" && (data.bold = settings(["text_bold", "bold"])); isNaN(data.size) && (data.size = settings(["text_size", "size"])); data.font && (data.font_family = data.font); !data.font_family && (data.font_family = settings(["text_font_family", "text_font", "font"])); !data.align && (data.align = settings(["text_align", "align"])); isNaN(data.x) && (data.x = settings(["text_x", "x"])); isNaN(data.y) && (data.y = settings(["text_y", "y"])); !data.text && (data.text = ""); isNaN(data.alpha) && (data.alpha = settings(["text_alpha", "alpha"])); !data.baseline && (data.baseline = settings(["text_baseline", "baseline"])); data.ok = true; }; context.save(); data.shadow && (shadow(data.shadow, context)); context.globalAlpha = data.alpha; context.textAlign = data.align; context.textBaseline = data.baseline; context.font = ( (data.italic ? "italic " : "") + (data.bold ? isNaN(data.bold) ? "bold " : data.bold + " " : "") + (dx < dy ? _x : _y)(data.size) + "px " + data.font_family ); if(data.color){ context.fillStyle = data.color; context.fillText(data.text, _x(data.x + mx), _y(data.y + my)); }; if(data.border){ border(data.border, context); context.strokeText(data.text, _x(data.x + mx), _y(data.y + my)); }; context.restore(); }, block : (data, context, kanvas, o) => { if(data.ignore) return; if(!data.ok){ isNaN(data.alpha) && (data.alpha = settings(["block_alpha", "alpha"])); data.ok = true; }; context.save(); context.globalAlpha = data.alpha; draw(data.childs, context, {mx : 0, my : 0}); context.restore(); } }; this.add_components = (json, overwrite) => { if(!json) return; !json.push && (json = [json]); typeof overwrite != "bool" && (overwrite = settings("overwrite")); json.forEach((items) => { if(!items) return; if(items.push) self.add_components(items, overwrite); else if(typeof items == "object") for(let key in items) (overwrite || !components[key]) && (components[key] = items[key]); }); }; this.check_mouse = (item, event) => { item_self.setAttribute("data-mouse-x", mouse.x = (event.clientX * q * qx / s) - mx); item_self.setAttribute("data-mouse-y", mouse.y = (event.clientY * q * qy / s) - my); execute_event("mouse_move"); }; const on_event = this.on_event = (name, method, range) => { if(typeof method != "function") return null; !events[name] && (events[name] = { methods : [], l : 0, ranges : [] }); let i = 0; for(; i < events[name].l; i ++) if(!events[name].methods[i]) break; events[name].methods[i] = method; events[name].ranges[i] = range; events[name].l = events[name].methods.length; return i; }; const remove_event = this.remove_event = (name, i) => events[name] && !isNaN(i) && i >= 0 && i < events[name].l && events[name].methods[i] && (events[name].methods[i] = null); const execute_event = this.execute_event = (name, input) => { if(events[name] && events[name].l) for(let i = 0; i < events[name].l; i ++) events[name].methods[i] && events[name].methods[i](range_analyze(events[name].ranges[i]), input); }; const event_range = this.event_range = (name, i) => events[name] && !isNaN(i) && i >= 0 && i < events[name].l ? events[name].ranges[i] : null; this.on_mouse_move = (method, range) => on_event("mouse_move", method, range); this.on_resize = (method, range) => on_event("resize", method, range); this.on_click = (method, range) => on_event("click", method, range); this.on_key_down = (method, range) => on_event("key_down", method, range); this.on_key_up = (method, range) => on_event("key_up", method, range); this.remove_mouse_move = i => {remove_event("mouse_move", i);}; this.remove_resize = i => {remove_event("resize", i);}; this.remove_click = i => {remove_event("click", i);}; this.remove_key_down = i => {remove_event("key_down", i);}; this.remove_key_up = i => {remove_event("key_up", i);}; this.range_mouse_move = i => event_range("mouse_move", i); this.range_resize = i => event_range("resize", i); this.range_click = i => event_range("click", i); this.range_key_down = i => event_range("key_down", i); this.range_key_up = i => event_range("key_up", i); this.key_down = (item, event) => execute_event("key_down", {code : event.keyCode}); this.key_up = (item, event) => execute_event("key_up", {code : event.keyCode}); this.get_margins = () => {return {x : mx, y : my};}; this.get_position = () => {return {x : position.x, y : position.y};}; this.get_cells = () => c; this.get_canvas_distance = () => {return {x : dx, y : dy};}; this.get_cell_size = () => s; construct(); };