From b7cd8677781c79ab9c2fd4418fb5bdc9cf55a8f7 Mon Sep 17 00:00:00 2001 From: KyMAN <0kyman0@gmail.com> Date: Sat, 16 Mar 2024 21:55:23 +0100 Subject: [PATCH] fix(ecma): Fixing the default Kanvas ECMA file. --- Public/ecma/Kanvas.ecma.js | 747 +++++++++++++++++++++++++++++++++++++ 1 file changed, 747 insertions(+) create mode 100644 Public/ecma/Kanvas.ecma.js diff --git a/Public/ecma/Kanvas.ecma.js b/Public/ecma/Kanvas.ecma.js new file mode 100644 index 0000000..144f9e9 --- /dev/null +++ b/Public/ecma/Kanvas.ecma.js @@ -0,0 +1,747 @@ +Kanvas = function(settings){ + + const self = this, + default_settings = { + nulls : false, + default_value : null, + position : ".kanvas", + preload_timeout : 2000, + frames_per_second : 60, + quality : 1, + quality_x : 1, + quality_y : 1, + cells : 40, + swap_and_drop_timer : 5000, + font_size : 1, + font_family : "Arial", + settings_overwrite : false, + autostart : true, + autobuild : true, + font_minimum_size : 12, + events_cache_timer : 100 + }, + custom = {}, + cache = {}, + frames_times = [], + id_length = 11, + id_alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", + ids = [], + threads = [], + number_of_cells = {x : 0, y : 0}, + events_cache = []; + let thread = null, + frames_per_second = null, + canvas, context, + last_frame_time = 0, frames_times_summatory = 0, + swap_and_drop_timer, + cache_box, + default_font_size, default_font_family, + settings_overwrite, + built = false, + started = false, + font_minimum_size, + events_cache_timer; + + this.map = []; + let cells = this.cells; + let cell_size = this.cell_size; + let quality = this.quality; + let quality_x = this.quality_x; + let quality_y = this.quality_y; + this.cells_x = 0; + this.cells_y = 0; + this.delta_time = 0; + + let item_self = this.item_self; + let hash_self = this.hash_self; + let object_name = this.object_name; + + this.Event = function(){ + + const self = this, + events = [], + properties = []; + + const construct = () => { + + events_cache.push(autoclean); + + }; + + this.execute = (callback, ...arguments) => events.forEach((event, i) => ( + event && + (typeof callback == "function" ? callback(properties[i]) : true) && + event(...arguments) + )); + + this.add = (callback, own_properties) => { + + let i = 0; + const l = events.length; + + for(; i < l; i ++) + if(!events[i]) + break; + + events[i] = callback; + properties[i] = own_properties; + + return i; + }; + + this.remove = i => events[i] = null; + + const autoclean = () => { + + const date = Date.now(); + + properties.forEach((own_properties, i) => { + if(own_properties && own_properties.last_used && date - own_properties.last_used > events_cache_timer){ + events[i] = null; + properties[i] = null; + }; + }); + + }; + + this.update = (i, date) => events[i] && (properties[i].last_used = date); + + construct(); + + }; + + this.on_screen_change = new this.Event(); + this.on_ready = new this.Event(); + this.on_click = new this.Event(); + this.on_click_down = new this.Event(); + this.on_click_up = new this.Event(); + this.on_key_down = new this.Event(); + this.on_key_up = new this.Event(); + + const construct = () => { + + settings = ( + settings instanceof Array ? settings : + typeof settings == "object" ? [settings] : + []).filter(inputs => typeof inputs == "object" && !(inputs instanceof Array)); + + self.settings("autostart") && self.start(); + + }; + + this.start = callback => { + + const end = status => typeof callback == "function" && callback(status); + + if(started){ + end(false); + return false; + }; + started = true; + + settings_overwrite = self.settings(["settings_overwrite", "overwrite"]); + frames_per_second = self.settings(["frames_per_second", "fps"]); + events_cache_timer = self.settings("events_cache_timer"); + object_name = self.object_name = self.settings("object_name"); + + thread = setInterval(execute, 1000 / frames_per_second); + + if(self.settings("autobuild")) + self.build(callback); + else + end(true); + + return true; + }; + + this.build = callback => { + + const position = self.settings("position"), + end = status => typeof callback == "function" && callback(status); + + if(built){ + end(false); + return false; + }; + built = true; + + if(position){ + + if(position.tagName || position.nodeName){ + end_build(position); + end(true); + return; + }; + + if(typeof position != "string"){ + console.error("position_not_string"); + end(false); + return; + }; + + if(!position.trim()){ + console.error("position_selector_empty"); + end(false); + return; + }; + + let html_object; + + try{ + if(html_object = document.querySelector(position)){ + end_build(html_object); + end(true); + return; + }; + }catch(exception){ + console.error(exception); + console.error("position_bad_selector"); + end(false); + return; + }; + + const date = Date.now(), + timeout = self.settings("preload_timeout"); + let interval = setInterval(() => { + if(html_object = document.querySelector(position)){ + clearInterval(interval); + end_build(html_object); + end(true); + }else if(Date.now() - date > timeout){ + clearInterval(interval); + console.error("position_timeout"); + end(false); + }; + }, frames_per_second); + + }else{ + console.error("no_position"); + end(false); + }; + + return true; + }; + + const end_build = position => { + + quality = self.quality = self.settings("quality"); + quality_x = self.quality_x = self.settings("quality_x"); + quality_y = self.quality_y = self.settings("quality_y"); + cells = self.cells = self.settings("cells"); + default_font_size = self.settings("font_size"); + default_font_family = self.settings("font_family"); + + cache.quality = 0; + cache.quality_x = 0; + cache.quality_y = 0; + + cache.screen = {x : 0, y : 0}; + cache.origin = {x : 0, y : 0}; + + item_self = self.item_self = (position || document.querySelector("body")).appendChild(document.createElement("div")); + cache_box = item_self.appendChild(document.createElement("div")); + canvas = item_self.appendChild(document.createElement("canvas")); + hash_self = self.hash_self = self.settings(["id", "hash"]) || self.create_id(); + + item_self.setAttribute("id", hash_self); + item_self.setAttribute("class", ["kanvas", hash_self].concat((self.settings("class") || "").split(/\s+/)).filter((key, i, array) => array.indexOf(key) == i).join(" ")); + item_self.setAttribute("data-hash", hash_self); + item_self.setAttribute("data-cells", cells); + item_self.setAttribute("data-minimum-font-size", font_minimum_size = self.settings("font_minimum_size")); + + cache_box.setAttribute("class", "kanvas-cache-box"); + cache_box.setAttribute("style", ` + position : absolute; + top : 0%; + left : 0%; + width : 100%; + height : 100%; + visibility : hidden; + z-index : 10; + opacity : 0; + `.replace(/[\r\n\s]+/g, "")); + + canvas.setAttribute("class", "kanvas-ui"); + canvas.setAttribute("style", ` + position : absolute; + top : 0%; + left : 0%; + width : 100%; + height : 100%; + z-index : 20; + `.replace(/[\r\n\s]+/g, "")); + canvas.onclick = event => on_click(canvas, event, "click"); + canvas.onmousedown = event => on_click(canvas, event, "down"); + canvas.onmouseup = event => on_click(canvas, event, "up"); + canvas.onkeydown = event => on_key(canvas, event, "down"); + canvas.onkeyup = event => on_key(canvas, event, "up"); + + context = canvas.getContext("2d"); + + context.id = self.create_id(); + + swap_and_drop_timer = self.settings("swap_and_drop_timer"); + + self.on_ready.execute(); + + }; + + this.nulls = nulls => typeof nulls == "boolean" ? nulls : self.settings("nulls", null, false, false); + + this.default_value = (_default, nulls) => _default !== undefined && (self.nulls(nulls) || _default !== null) ? _default : self.settings("default_value", null, null, true); + + this.settings = (names, inputs, _default, nulls) => { + + const l = (names = ( + names instanceof Array ? names : + typeof names == "string" ? [names] : + [] + ).filter((name, i, array) => name && typeof name == "string" && array.indexOf(name) == i)).length; + + if(l){ + + const m = (inputs = ( + inputs instanceof Array ? inputs : + typeof inputs == "object" ? [inputs] : + []).concat(settings, custom, [default_settings])).length; + + nulls = self.nulls(nulls); + + for(let j = 0; j < m; j ++) + if(inputs[j] && typeof inputs[j] == "object" && !(inputs[j] instanceof Array)) + for(let i = 0; i < l; i ++) + if(inputs[j][names[i]] !== undefined && (nulls || inputs[j][names[i]] !== null)) + return inputs[j][names[i]]; + }; + return self.default_value(_default, nulls); + }; + + this.settings_add = (inputs, overwrite) => { + if(!inputs) + return; + + if(typeof inputs == "string"){ + try{ + inputs = JSON.parse(inputs); + }catch(exception){}; + }; + + if(typeof inputs == "object"){ + if(inputs instanceof Array) + inputs.forEach(inputs, overwrite); + else{ + typeof overwrite != "boolean" && (overwrite = settings_overwrite); + for(const key in inputs) + if(overwrite || custom[key] === undefined) + custom[key] = inputs[key]; + }; + }; + + }; + + this.create_id = () => { + + let id; + const l = id_alphabet.length; + + do{ + id = ""; + while((id += id_alphabet[l * Math.random() >> 0]).length < id_length); + }while( + ids.includes(id) || + !/^[a-z]/i.test(id) || + document.querySelector("." + id + ",#" + id + ",[name=" + id + "]") + ); + ids.push(id); + + return id; + }; + + const execute = () => { + + const date = Date.now(); + let screen_changed = false; + + if(item_self && (cache.screen.x != item_self.offsetWidth || cache.screen.y != item_self.offsetHeight)){ + screen_changed = true; + + cache.screen.x = item_self.offsetWidth; + cache.screen.y = item_self.offsetHeight; + + const font_size = cache.screen[cache.screen.x < cache.screen.y ? "x" : "y"] / cells; + + item_self.style.fontSize = (font_size < font_minimum_size ? font_minimum_size : font_size) + "px"; + + }; + + if(canvas){ + + if(last_frame_time){ + + const frame_time = date - last_frame_time; + + frames_times.push(frame_time); + frames_times_summatory += frame_time; + + self.delta_time = frame_time / 1000; + + while(frames_times.length > frames_per_second) + frames_times_summatory -= frames_times.shift(); + + }; + + last_frame_time = date; + + if(screen_changed || cache.quality != quality){ + + const width = cache.screen.x * quality, + height = cache.screen.y * quality; + + cache.quality = quality; + canvas.setAttribute("width", width); + canvas.setAttribute("height", height); + cache.origin.x = width / 2; + cache.origin.y = height / 2; + + cell_size = self.cell_size = (width > height ? height : width) / cells; + + number_of_cells.x = width / cell_size; + number_of_cells.y = height / cell_size; + + this.cells_x = number_of_cells.x / 2; + this.cells_y = number_of_cells.y / 2; + + for(const key in cache) + if(cache[key] && cache[key].data) + cache[key].data = null; + + self.on_screen_change.execute(); + + }; + + }; + + threads.forEach(thread => thread && thread()); + + if(canvas){ + + context.beginPath(); + context.clearRect(0, 0, cache.screen.x * quality, cache.screen.y * quality); + context.translate(cache.origin.x, cache.origin.y); + + draw(context, self.map, 0, 0); + + context.translate(-cache.origin.x, -cache.origin.y); + + for(const key in cache) + if(cache[key] && cache[key].last_used && date - cache[key].last_used > swap_and_drop_timer) + delete cache[key]; + + }; + + events_cache.forEach(autoclean => autoclean()); + + }; + + this.set_quality = new_quality => quality = self.quality = new_quality; + this.set_quality_x = new_quality => quality_x = self.quality_x = new_quality; + this.set_quality_y = new_quality => quality_y = self.quality_y = new_quality; + + const _x = x => x * cell_size; + const _y = y => y * cell_size; + const size = size => size * cell_size; + + const set_cache = (context, status) => { + + !status && cache[context.id] && cache[context.id].ok && (cache[context.id].ok = false); + + return status; + }; + + const set_border = (context, inputs) => { + + const has_border = !!(inputs.border_color || !isNaN(inputs.border_width)); + + inputs.border_color && (context.strokeStyle = inputs.border_color); + !isNaN(inputs.border_width) && (context.lineWidth = size(inputs.border_width)); + + return has_border; + }; + + const set_background = (context, inputs) => { + + const has_background = !!(inputs.background || (!inputs.border_color && isNaN(inputs.border_width))); + + if(inputs.background){ + if(inputs.background instanceof Array){ + + const v = inputs.background, + is_linear = v.length == 5, + gradient = ( + is_linear ? context.createLinearGradient(_x(v[0]), _y(v[1]), _x(v[2]), _y(v[3])) : + context.createRadialGradient(_x(v[0]), _y(v[1]), _x(v[2]), _y(v[3]), size(v[4]), size(v[5])) + ); + + inputs.background[is_linear ? 4 : 6].forEach(color => gradient.addColorStop(color[0], color[1])); + + context.fillStyle = gradient; + + }else + context.fillStyle = inputs.background; + }; + + return has_background; + }; + + const set_shadow = (context, inputs, shape_callback) => { + + const shadows = inputs.shadow || inputs.shadows; + + (shadows && shadows.length ? shadows[0] instanceof Array ? shadows : [shadows] : []).forEach(shadow => { + [context.shadowOffsetX, context.shadowOffsetY, context.shadowBlur, context.shadowColor] = shadow; + context.shadowOffsetX = size(shadow[0]); + context.shadowOffsetY = size(shadow[1]); + context.shadowBlur = size(shadow[2]); + context.shadowColor = size(shadow[3]); + shape_callback(); + }); + + }; + + const shapes = { + rectangle : (context, inputs) => { + + const x = _x(inputs.x), + y = _y(inputs.y), + width = size(inputs.width), + height = size(inputs.height), + has_border = set_border(context, inputs), + has_background = set_background(context, inputs); + + set_shadow(context, inputs, () => context.rect(x, y, width, height)); + has_background && context.fillRect(x, y, width, height); + has_border && context.strokeRect(x, y, width, height); + + return set_cache(context, true); + }, + image : (context, inputs) => { + + const url = inputs.url; + + if(url){ + + const cached = cache[url]; + + if(!cached){ + (cache[url] = { + image : new Image(), + loaded : false, + last_used : Date.now() + }).image.src = url; + cache[url].image.crossOrigin = "anonymous"; + cache[url].image.onload = () => cache[url].loaded = true; + return set_cache(context, false); + }; + + if(cached.loaded){ + + let width = inputs.width, + height = inputs.height; + const cut_x = inputs.cut_x || 0, + cut_y = inputs.cut_y || 0, + cut_width = inputs.cut_width || 0, + cut_height = inputs.cut_height || 0, + position_x = inputs.x || 0, + position_y = inputs.y || 0, + x = _x(position_x), + y = _y(position_y), + end_width = size(width), + end_height = size(height); + + !width && (width = cache.quality * (cut_width || cache[url].image.width - cut_x) / cell_size); + !height && (height = cache.quality * (cut_height || cache[url].image.height - cut_y) / cell_size); + + set_shadow(context, inputs, () => context.rect(x, y, end_width, end_height)); + set_border(context, inputs); + context.drawImage( + cache[url].image, + cut_x, cut_y, cut_width || cache[url].image.width - cut_x, cut_height || cache[url].image.height - cut_y, + x, y, end_width, end_height + ); + cache[url].last_used = Date.now(); + + return set_cache(context, true); + }; + + }; + + return set_cache(context, false); + }, + cache : (context, inputs) => { + + const width = inputs.width ? inputs.width * cell_size : canvas.getAttribute("width"), + height = inputs.height ? inputs.height * cell_size : canvas.getAttribute("height"); + let status = false; + + if(!cache[inputs.name]){ + + const subcanvas = cache_box.appendChild(document.createElement("canvas")); + + cache[inputs.name] = { + canvas : subcanvas, + data : null + }; + cache[inputs.name].context = subcanvas.getContext("2d"); + + cache[inputs.name].context.id = inputs.name; + + subcanvas.setAttribute("data-id", cache[inputs.name].context.id); + + cache[inputs.name].context.translate(inputs.x || 0, inputs.y || 0); + + }; + + if(cache[inputs.name].data){ + if(cache[inputs.name].image_loaded){ + context.drawImage(cache[inputs.name].image, _x(inputs.x || 0), _y(inputs.y || 0), cache[inputs.name].image.width, cache[inputs.name].image.height); + status = true; + }else if(!cache[inputs.name].image){ + + cache[inputs.name].image = new Image(); + + cache[inputs.name].image.src = cache[inputs.name].data; + cache[inputs.name].image.onload = () => { + // cache[inputs.name].canvas.remove(); + cache[inputs.name].image_loaded = true; + }; + // cache[inputs.name].image.onerror = () => cache[inputs.name].canvas.remove(); + + }; + }else{ + + cache[inputs.name].canvas.setAttribute("width", width); + cache[inputs.name].canvas.setAttribute("height", height); + + cache[inputs.name].context.beginPath(); + cache[inputs.name].context.clearRect(0, 0, width, height); + cache[inputs.name].context.translate(width / 2, height / 2); + + cache[inputs.name].image = null; + cache[inputs.name].image_loaded = false; + cache[inputs.name].ok = true; + + draw(cache[inputs.name].context, inputs.childs, 0, 0); + + cache[inputs.name].context.closePath(); + + cache[inputs.name].ok && + (cache[inputs.name].data = cache[inputs.name].canvas.toDataURL("image/png", 1.0)); + + }; + + cache[inputs.name].last_used = Date.now(); + + return set_cache(context, status); + }, + text : (context, inputs) => { + + const x = _x(inputs.x), + y = _y(inputs.y), + has_border = set_border(context, inputs), + has_background = set_background(context, inputs); + + inputs.align && (context.textAlign = inputs.align); + inputs.baseline && (context.textBaseline = inputs.baseline); + !isNaN(inputs.border_width) && (context.lineWidth = size(inputs.border_width)); + context.font = (inputs.style ? inputs.style + " " : "") + size(inputs.size || default_font_size) + "px " + (inputs.family || default_font_family); + + set_shadow(context, inputs, () => context.fillText(inputs.text, x, y)); + has_background && context.fillText(inputs.text, x, y); + has_border && context.strokeText(inputs.text, x, y); + + return true; + } + }; + + this.string_variables = (string, variables) => string.replace(/\{([^\{\}]+)\}/g, (...arguments) => variables[arguments[1]] !== undefined ? variables[arguments[1]] : arguments[0]); + + const draw = (context, level, x, y) => level.forEach(values => { + if(values && (shapes[values.type] || ["block"].includes(values.type))){ + + const sub_x = _x(x + (values.margin_x || 0)), + sub_y = _y(y + (values.margin_y || 0)), + date = Date.now(); + + context.save(); + context.translate(sub_x, sub_y); + + const transform = context.getTransform(); + + values.rotate && context.rotate(2 * Math.PI * values.rotate / 360); + !isNaN(values.alpha) && (context.globalAlpha = values.alpha); + + if(values.on_click){ + values.context_x = transform.e / quality; + values.context_y = transform.f / quality; + if(!values.last_used || date - values.last_used > events_cache_timer) + values.i = self.on_click.add(eval(self.string_variables(values.on_click, {object_name : object_name})), values); + else + self.on_click.update(values.i, date); + values.last_used = date; + }; + + values.type != "block" && shapes[values.type](context, values); + + values.type != "cache" && values.childs && draw(context, values.childs, values.x, values.y); + + context.translate(-sub_x, -sub_y); + context.restore(); + + }; + }); + + this.get_real_fps = this.get_real_frames_per_second = () => frames_times_summatory ? 1000 / (frames_times_summatory / frames_times.length) : 0; + + this.threads_add = callback => { + + let i = 0; + const l = threads.length; + + for(; i < l; i ++) + if(!threads[i]) + break; + + threads[i] = callback; + + return i; + }; + + this.threads_remove = i => threads[i] = null; + + this.get_cells_x = () => number_of_cells.x; + this.get_cells_y = () => number_of_cells.y; + + this.extends = object => { + for(const key in self) + if(object[key] === undefined) + object[key] = self[key]; + }; + + const on_click = (canvas, event, action) => { + + switch(action){ + case "click": + self.on_click.execute(properties => ( + event.clientX >= properties.context_x + (properties.x * cell_size) && + event.clientY >= properties.context_y + (properties.y * cell_size) && + event.clientX <= properties.context_x + ((properties.x + properties.width) * cell_size) && + event.clientY <= properties.context_y + ((properties.y + properties.height) * cell_size) + ), canvas, event); + break; + }; + + }; + + construct(); + +}; \ No newline at end of file