"use strict"; import {Kanvas} from "../Kanvas.ecma.js"; /** * @class * @constructor * @param {!Kanvas} kanvas * @returns {void} * @access public */ export const Parallax = (function(){ /** * @constructs Parallax * @param {!Kanvas} kanvas * @returns {void} * @access private */ const Parallax = function(kanvas){ /** @type {Parallax} */ const self = this; /** * @returns {void} * @access private */ const constructor = () => {}; constructor(); }; /** * @class * @constructor * @param {?(Object.|Array.)} [inputs = null] * @returns {void} * @access public * @static */ Parallax.Position = function(inputs = null){ /** @type {Parallax.Position} */ const self = this; /** @type {number} */ this.x = Kanvas.get_value("x", inputs, 0); /** @type {number} */ this.y = Kanvas.get_value("y", inputs, 0); /** @type {number} */ this.limit_x = Kanvas.get_value("limit_x", inputs, 1); /** @type {number} */ this.limit_y = Kanvas.get_value("limit_y", inputs, 1); /** * @returns {void} * @access private */ const fix_x = () => { self.x = (self.x + self.limit_x) % self.limit_x; }; /** * @returns {void} * @access private */ const fix_y = () => { self.y = (self.y + self.limit_y) % self.limit_y; }; /** * @param {!number} x * @returns {void} * @access public */ this.add_x = x => { if(Kanvas.is_number(x)){ self.x += x; fix_x(); }; }; /** * @param {!number} y * @returns {void} * @access public */ this.add_y = y => { if(Kanvas.is_number(y)){ self.y += y; fix_y(); }; }; /** * @param {!number} x * @returns {void} * @access public */ this.set_x = x => { if(Kanvas.is_number(x)){ self.x = x; fix_x(); }; }; /** * @param {!number} y * @returns {void} * @access public */ this.set_y = y => { if(Kanvas.is_number(y)){ self.y = y; fix_y(); }; }; /** * @returns {number} * @access public */ this.get_x = () => self.x || 0; /** * @returns {number} * @access public */ this.get_y = () => self.y || 0; /** * @param {!number} x * @param {!number} y * @returns {void} * @access public */ this.add = (x, y) => { self.add_x(x); self.add_y(y); }; /** * @param {!number} x * @param {!number} y * @returns {void} * @access public */ this.set = (x, y) => { self.set_x(x); self.set_y(x); }; /** * @returns {Array.} * @access public */ this.get = () => [self.get_x(), self.get_y()]; }; /** * @class * @constructor * @param {!Kanvas} kanvas * @param {?Parallax.Class} [parent = null] * @param {?(Object.|Array.)} [inputs = null] * @returns {void} * @access public * @static */ Parallax.Stage = function(kanvas, parent = null, inputs = null){ /** @type {Parallax.Stage} */ const self = this; /** @type {Parallax.Class|null} */ this.parent = parent; /** @type {number} */ this.width = Kanvas.get_value(["width", "size", "x"], inputs, kanvas.cells); /** @type {number} */ this.height = Kanvas.get_value(["height", "size", "y"], inputs, kanvas.cells); /** @type {Array.|Array.|Kanvas.ObjectBase|null>} */ this.childs = Kanvas.get_array(Kanvas.get_value("childs", inputs, [])); /** @type {boolean} */ this.fixed = !!Kanvas.get_value("fixed", inputs, false); /** @type {Array.} */ this.framed_in = Kanvas.get_value("framed_in", inputs, [1, 1]); /** @type {boolean} */ this.repeated = !!Kanvas.get_value("repeated", inputs, false); /** @type {Array., HTMLCanvasElement|null>>} */ this.frames = []; /** @type {boolean} */ this.done = false; /** @type {number} */ this.childs_margin_x = Kanvas.get_value("childs_margin_x", 0); /** @type {number} */ this.childs_margin_y = Kanvas.get_value("childs_margin_y", 0); /** @type {number} */ this.start_x = Kanvas.get_value("start_x", inputs, kanvas.start_x); /** @type {number} */ this.start_y = Kanvas.get_value("start_y", inputs, kanvas.start_y); /** * @returns {void} * @access private */ const constructor = () => { self.rebuild(); }; /** * @returns {void} * @access public */ this.build_frames = () => { /** @type {[number, number]} */ const [frames_x, frames_y] = self.framed_in, /** @type {[number, number]} */ [size_x, size_y] = [frames_x, frames_y].map(value => 1 / value); self.frames = []; for(let x = 0, _x; x < frames_x; x ++) for(let y = 0, _x = x + 1; y < frames_y;) self.frames.push([x * size_x, y * size_y, _x * size_x, ++ y * size_y, [], null]); }; /** * @param {!HTMLCanvasElement} canvas * @returns {void} * @access private */ const set_canvas_size = canvas => { canvas.setAttribute("width", self.width * kanvas.cell_size * kanvas.quality); canvas.setAttribute("height", self.height * kanvas.cell_size * kanvas.quality); }; /** * @param {!number} i * @returns {Array.} * @access public */ this.get_frame_position = i => [i % self.framed_in[Parallax.X], i / self.framed_in[Parallax.Y] >> 0]; /** * @param {!(Object.|Kanvas.ObjectBase)} item * @returns {Array., Array.>} * @access private */ const get_item_range_position = item => { /** @type {Array., Array.>} */ const ranges = [ [item.x - item.width * item.align_x, item.x + item.width * (1 - item.align_x)], [item.y - item.height * item.align_y, item.y + item.height * (1 - item.align_y)] ]; ["width", "height"].forEach((key, i) => { ranges[i][0] < 0 && (ranges[i][0] += self[key]); ranges[i][1] %= self[key]; }); return ranges; }; /** * @param {!Array.} ks * @returns {void} * @access public */ this.recalculate_items_in_frames = (ks = []) => { /** @type {Array.} */ const frame_size = [self.width / framed_in[Parallax.X], self.height / framed_in[Parallax.Y]], /** @type {number} */ l = self.frames.length; Kanvas.get_array(ks).forEach(k => { /** @type {Array., Array.>} */ const [x_positions, y_positions] = get_item_range_position(self.childs[k]).map((range, i) => ( Parallax.get_range_positions(range.map(value => value / frame_size[i] >> 0), framed_in[i]) )); self.frames.forEach(frame => { frame.includes(k) && frame.splice(frame.indexOf(k), 1); }); x_positions.forEach(x => { y_positions.forEach(y => { /** @type {number} */ const i = y * framed_in[0] + x; self.frames[i] && self.frames[i].push(k); }); }); }); }; // /** // * @returns {void} // * @access public // */ // this.recalculate_frames_items = () => { // self.recalculate_items_in_frames(Object.keys(self.childs)); // }; this.build_cache = callback => { // if(self.fixed){ // /** @type {number} */ // let loaded = 0, // /** @type {number // * } */ // items_l = 0; // /** @type {number} */ // const l = self.frames.length, // end = () => { // ++ loaded == l && Kanvas.execute(callback); // }; // self.done = false; // self.frames.forEach(([from_x, from_y, to_x, to_y, childs, cache], i) => { // /** @type {HTMLCanvasElement} */ // const canvas = cache || document.createElement("canvas"), // /** @type {CanvasRenderingContext2D} */ // context = canvas.getContext("2d"), // /** @type {[number, number]} */ // [main_x, main_y] = self.get_frame_position(i); // set_canvas_size(canvas); // cache || (self.frames[i][Parallax.CACHE] = canvas); // Parallax.FRAMES_BROTHERS.forEach(plus_x => { // /** @type {number} */ // const x = (main_x + plus_x + self.framed_in[Parallax.X]) % self.framed_in[Parallax.X]; // Parallax.FRAMES_BROTHERS.forEach(plus_y => { // /** @type {number} */ // const y = (main_y + plus_y + self.framed_in[Parallax.Y]) % self.framed_in[Parallax.Y], // /** @type {number} */ // j = y * self.framed_in[Parallax.X] + x; // kanvas.draw(context, self.frames[j][Parallax.CHILDS].map(k => { // items_l += Parallax.set_on_load(self.childs[k], end, (i, item) => { // self.childs[k] = item; // }); // return self.childs[k]; // }), [{ // child_margin_x : -from_x * self.width, // child_margin_y : -from_y * self.height // }, self.parent]); // }); // }); // items_l || end(); // }); // }; }; /** * @returns {void} * @access public */ this.resize_cache = () => { self.fixed && self.frames.forEach(([from_x, from_y, to_x, to_y, childs, cache]) => { set_canvas_size(cache); }); }; /** * @param {?kanvas_default_callback} [callback = null] * @returns {void} * @access public */ this.rebuild = (callback = null) => { self.build_frames(); self.build_cache(() => { self.resize_cache(); Kanvas.execute(callback); }); }; constructor(); }; /** * @class * @param {!Kanvas} kanvas * @returns {void} * @access public * @static */ Parallax.Class = 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"], array : ["stages", "childs"] }); this.type_name = "Parallax"; this.type = Kanvas.SHAPES.Parallax; /** @type {Parallax.Position} */ this.position = new Parallax.Position(); /** @type {Array.} */ this.stages = Kanvas.get_array(this._get("stages", [])).map(item => ( Parallax.is_stage(item) ? item : Kanvas.is_array(item) || Kanvas.is_dictionary(item) ? new Parallax.Stage(this.kanvas, this, item) : null)); this.set_status(Kanvas.LOADED); }; /** * @param {!CanvasRenderingContext2D} context * @param {!number} x * @param {!number} y * @returns {void} * @access public */ draw(context, x, y){ // try{ // this.stages.forEach(stage => { // /** @type {number} */ // const x = stage.width * this.position.x, // /** @type {number} */ // y = stage.height * this.position.y; // stage.frames.forEach(([from_x, from_y, to_x, to_y, childs, cache], i) => { // /** @type {Array.} */ // const ks = []; // from_x *= stage.width; // to_x *= stage.width; // from_y *= stage.height; // to_y *= stage.height; // if(x >= from_x && x <= to_x && y >= from_y && y >= to_y){ // console.log(stage.fixed); // if(stage.fixed) // cache && context.drawImage(cache, from_x - x, from_y - y); // else // childs.forEach(k => { // if(!ks.includes(k)){ // ks.push(k); // }; // }); // }; // stage.fixed || // this.kanvas.draw(context, ks.sort((a, b) => a - b).map(k => stage.childs[k]), [{ // margin_x : from_x - x, // margin_y : from_y - y, // without_parent : true // }, this]); // }); // }); // }catch(exception){ // console.error(exception); // }; }; }; /** @type {number} */ Parallax.X = 0; /** @type {number} */ Parallax.Y = 1; /** @type {number} */ Parallax.FROM_X = 0; /** @type {number} */ Parallax.FROM_Y = 1; /** @type {number} */ Parallax.TO_X = 2; /** @type {number} */ Parallax.TO_Y = 3; /** @type {number} */ Parallax.CHILDS = 4; /** @type {number} */ Parallax.CACHE = 5; /** @type {Array.} */ Parallax.FRAMES_BROTHERS = [-1, 0, 1]; /** * @param {?any} item * @returns {boolean} * @access public * @static */ Parallax.is_stage = item => item instanceof Parallax.Stage; /** * @param {!(Object.|Array.|Kanvas.ObjectBase|null)} item * @param {!kanvas_default_callback} callback * @param {?kanvas_event_callback} [child_callback = null] * @returns {number} * @access public */ Parallax.set_on_load = (item, callback, child_callback = null) => { /** @type {number} */ let i = 0; const end = () => { Kanvas.execute(callback); Kanvas.execute(child_callback); }; if(item){ if(Kanvas.is_array(item)) item.forEach(sub_item => i += Parallax.set_on_load(sub_item, callback)); else{ const callbacks = [callback, child_callback]; i = 1; if(item.status && item.status & Kanvas.LOADED) setTimeout(end, 0); else if(Kanvas.is_dictionary){ item.on_load && callbacks.push(item.on_load); item.on_load = (j, item_loaded) => { callbacks.forEach(subcallback => Kanvas.execute(subcallback, j, item_loaded)); }; }else item.on_load.add(end); }; }; return i; }; /** * @param {!Array.} range * @param {!number} limit * @returns {Array.} * @access public * @static */ Parallax.get_range_positions = (range, limit) => { /** @type {Array.} */ const positions = []; if(range[0] > range[1]){ for(let i = 0; i <= range[0]; i ++) positions.push(i); for(let i = range[1]; i < limit; i ++) positions.push(i); }else for(let i = range[0]; i <= range[1]; i ++) positions.push(i); return positions; }; return Parallax; })(); Kanvas.SHAPES.Parallax = Parallax.Class;