scrawl-canvas
Version:
Responsive, interactive and more accessible HTML5 canvas elements. Scrawl-canvas is a JavaScript library designed to make using the HTML5 canvas element easier, and more fun
281 lines (180 loc) • 8.26 kB
JavaScript
// # Cascade mixin
// This mixin sets up the Scrawl-canvas functionality required to perform its __Display cycle__.
// + The Display cycle is initiated by [Stack](../factory/stack.html) and DOM-related [Cell](../factory/cell.html) wrappers (generally [Canvas](../factory/canvas.html) wrappers) - __controller objects__
// + The cycle cascades from the wrappers to all artefact objects associated with them, as mediated by [Group](../factory/group.html) objects.
//
// The mixin also includes code to assist __drag-and-drop__ functionality.
// #### Imports
import { group } from '../core/library.js';
import { mergeOver, pushUnique, removeItem, xtGet, Ωempty } from '../helper/utilities.js';
import { releaseArray, requestArray } from '../helper/array-pool.js';
// Shared constants
import { _floor, REVERSE_BY_DELTA, UPDATE_BY_DELTA } from '../helper/shared-vars.js';
// Local constants
const ADD_ARTEFACT_CLASSES = 'addArtefactClasses',
REMOVE_ARTEFACT_CLASSES = 'removeArtefactClasses',
SET_ARTEFACTS = 'setArtefacts',
UPDATE_ARTEFACTS = 'updateArtefacts';
// #### Export function
export default function (P = Ωempty) {
// #### Shared attributes
const defaultAttributes = {
// The __groups__ attribute holds the String names of all the Group objects associated with the controller object.
groups: null,
// The __groupBuckets__ attribute holds a reference to each Group object, in a set of arrays grouping Groups according to their order values.
groupBuckets: null,
// The __batchResort__ Boolean flag determines whether the Groups will be sorted by their order value before instructions get passed to each in sequence. Best to leave this flag alone to do its job.
batchResort: true,
};
P.defs = mergeOver(P.defs, defaultAttributes);
// #### Packet management
// No additional packet functionality required
// #### Clone management
// No additional clone functionality required
// #### Kill management
// No additional kill functionality required
// #### Get, Set, deltaSet
const G = P.getters,
S = P.setters;
// __groups__
G.groups = function () {
return [].concat(this.groups);
};
// Passes the first argument to the `addGroups` function. Not recommended
S.groups = function (item) {
this.groups.length = 0;
this.addGroups(item);
};
// #### Prototype functions
// `sortGroups` - internal function - Groups are sorted from the __groups__ Array into the __groupBuckets__ array using a bespoke bucket sort algorithm, based on each Group object's __order__ attribute
P.sortGroups = function () {
if (this.batchResort) {
this.batchResort = false;
const {groups, groupBuckets} = this;
const buckets = requestArray();
let i, iz, obj, name, order, arr;
for (i = 0, iz = groups.length; i < iz; i++) {
name = groups[i];
obj = group[name];
if (obj) {
order = _floor(obj.order) || 0;
if (!buckets[order]) buckets[order] = requestArray();
buckets[order].push(obj);
}
}
groupBuckets.length = 0;
for (i = 0, iz = buckets.length; i < iz; i++) {
arr = buckets[i];
if (arr) {
groupBuckets.push(...arr);
releaseArray(arr);
}
}
releaseArray(buckets);
}
};
// `initializeCascade` - internal function used by factory constructors
P.initializeCascade = function () {
this.groups = [];
this.groupBuckets = [];
};
// ##### Group management
// Groups should be added to, and removed from, the controller object using the __addGroups__ and __removeGroups__ functions. The argument can be one or more Group object's name attribute, or the Group object(s) itself.
// `addGroups`
P.addGroups = function (...args) {
const groups = this.groups;
args.forEach(item => {
if (item.substring) pushUnique(groups, item);
else if (group[item]) pushUnique(groups, item.name);
}, this);
this.batchResort = true;
return this;
};
// `removeGroups`
P.removeGroups = function (...args) {
const groups = this.groups;
args.forEach( item => {
if (item.substring) removeItem(groups, item);
else if (group[item]) removeItem(groups, item.name);
}, this);
this.batchResort = true;
return this;
};
// `cascadeAction` - internal helper function used by the functions below
P.cascadeAction = function (items, action) {
let g;
this.groups.forEach(name => {
g = group[name];
if (g) g[action](items);
}, this);
return this;
};
// `updateArtefacts` - Update all artefact objects in all the controller object's Groups. The supplied argument will be passed on to each artefact's `setDelta` function.
P.updateArtefacts = function (items) {
this.cascadeAction(items, UPDATE_ARTEFACTS);
return this;
};
// `updateArtefacts` - Set all artefact objects in all the controller object's Groups. The supplied argument will be passed on to each artefact's `set` functions
P.setArtefacts = function (items) {
this.cascadeAction(items, SET_ARTEFACTS);
return this;
};
// `addArtefactClasses` - specific to DOM-related artefacts (Stack, Canvas, Element)
P.addArtefactClasses = function (items) {
this.cascadeAction(items, ADD_ARTEFACT_CLASSES);
return this;
};
// `removeArtefactClasses` - specific to DOM-related artefacts (Stack, Canvas, Element)
P.removeArtefactClasses = function (items) {
this.cascadeAction(items, REMOVE_ARTEFACT_CLASSES);
return this;
};
// `updateByDelta` - triggers the related artefact function, to update (add) its attributes by values held in its `delta` object attribute
P.updateByDelta = function () {
this.cascadeAction(false, UPDATE_BY_DELTA);
return this;
};
// `reverseByDelta` - triggers the related artefact function, to reverse (subtract) its attributes by values held in its `delta` object attribute
P.reverseByDelta = function () {
this.cascadeAction(false, REVERSE_BY_DELTA);
return this;
};
// ##### Collision detection
// The `getArtefactAt` function checks to see if any of the controller object's Groups' artefacts are located at the supplied coordinates in the argument object.
// + The first artefact to report back as being at that coordinate will be returned by the function
// + Where no artefacts are present at that coordinate the function returns false.
// + The artefact with the highest order attribute value will be returned first.
// + This function forms part of the Scrawl-canvas __drag-and-drop__ functionality.
P.getArtefactAt = function (items) {
items = xtGet(items, this.here, false);
if (items) {
let g, res;
for (let i = this.groups.length - 1; i >= 0; i--) {
g = group[this.groups[i]];
if (g) {
res = g.getArtefactAt(items);
if (res) return res;
}
}
}
return false;
};
// The `getAllArtefactsAt` function returns all of the controller object's Groups' artefacts located at the supplied coordinates in the argument object.
// + The artefact with the highest order attribute value will be returned first.
// + The function will always return an array of artefact objects, or an empty Array
P.getAllArtefactsAt = function (items) {
items = xtGet(items, this.here, false);
const results = [];
if (items) {
let g, res;
for (let i = this.groups.length - 1; i >= 0; i--) {
g = group[this.groups[i]];
if (g) {
res = g.getAllArtefactsAt(items);
if (res) results.push(...res);
}
}
}
return results;
};
}