UNPKG

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

390 lines (275 loc) 11.9 kB
// # Cell key functions mixin // This mixin defines shared functions for the Cell wrapper and CellFragment object // #### Imports import { Ωempty } from '../helper/utilities.js'; import { cell, cellnames, styles, stylesnames } from '../core/library.js'; import { releaseArray, requestArray } from '../helper/array-pool.js'; // Shared constants import { _cos, _entries, _isArray, _keys, _radian, _sin, BLANK, DRAW, FILL, LEFT, LINE_DASH, STATE_ALL_KEYS, TOP } from '../helper/shared-vars.js'; // Local constants // - None defined // #### Export function export default function (P = Ωempty) { // #### Prototype functions // The following functions set the Cell wrapper's &lt;canvas> element's CanvasRenderingContext2D engine's attributes to match a given set of values // `setEngineFromState` - internal function: set engine to match this Cell's State object's attribute values P.setEngineFromState = function (engine) { const state = this.state, len = STATE_ALL_KEYS.length; let i, key, eVal, sVal; for (i = 0; i < len; i++) { key = STATE_ALL_KEYS[i]; eVal = engine[key]; sVal = state[key]; if (key === LINE_DASH) { engine.lineDash = sVal; engine.setLineDash(engine.lineDash); } else if (eVal !== sVal) engine[key] = sVal; } if (engine.textAlign !== LEFT) engine.textAlign = LEFT; if (engine.textBaseline !== TOP) engine.textBaseline = TOP; return this; }; // `setToDefaults` - internal function: set engine to match the State Factory's default attribute values P.setToDefaults = function () { const items = this.state.defs, state = this.state, engine = this.engine; _entries(items).forEach(([key, val]) => { if (key === LINE_DASH) { if (!_isArray(engine.lineDash)) engine.lineDash = []; else engine.lineDash.length = 0; if (!_isArray(state.lineDash)) state.lineDash = []; else state.lineDash.length = 0; } else { engine[key] = val; state[key] = val; } }); engine.textAlign = state.textAlign = LEFT; engine.textBaseline = state.textBaseline = TOP; return this; }; // `setEngine` - internal function: set engine to match the entity object's State attribute values P.setEngine = function (myentity) { const state = this.state, entityState = myentity.state; if (entityState) { const changes = entityState.getChanges(myentity, state), action = this.setEngineActions; if (_keys(changes).length) { const engine = this.engine; for (const item in changes) { action[item](changes[item], engine, myentity, this); state[item] = changes[item]; } } } return myentity; }; // __setEngineActions__ - an Object containing functions for updating the engine's attributes; used by `setEngine` P.setEngineActions = { fillStyle: function (item, engine, myentity, layer) { if (item.substring) { let brokenStyle = false; if (stylesnames.includes(item)) brokenStyle = styles[item]; else if (cellnames.includes(item)) brokenStyle = cell[item]; if (brokenStyle) { myentity.state.fillStyle = brokenStyle; engine.fillStyle = brokenStyle.getData(myentity, layer, FILL); } else engine.fillStyle = item; } else engine.fillStyle = item.getData(myentity, layer, FILL); }, filter: function (item, engine) { engine.filter = item; }, font: function (item, engine) { engine.font = item; }, globalAlpha: function (item, engine) { engine.globalAlpha = item; }, globalCompositeOperation: function (item, engine) { engine.globalCompositeOperation = item; }, imageSmoothingEnabled: function (item, engine) { engine.imageSmoothingEnabled = item; }, imageSmoothingQuality: function (item, engine) { engine.imageSmoothingQuality = item; }, lineCap: function (item, engine) { engine.lineCap = item; }, lineDash: function (item, engine) { engine.lineDash = item; if (engine.setLineDash) engine.setLineDash(item); }, lineDashOffset: function (item, engine) { engine.lineDashOffset = item; }, lineJoin: function (item, engine) { engine.lineJoin = item; }, lineWidth: function (item, engine) { engine.lineWidth = item; }, miterLimit: function (item, engine) { engine.miterLimit = item; }, shadowBlur: function (item, engine) { engine.shadowBlur = item; }, shadowColor: function (item, engine) { engine.shadowColor = item; }, shadowOffsetX: function (item, engine) { engine.shadowOffsetX = item; }, shadowOffsetY: function (item, engine) { engine.shadowOffsetY = item; }, strokeStyle: function (item, engine, myentity, layer) { if (item.substring) { let brokenStyle = false; if (stylesnames.includes(item)) brokenStyle = styles[item]; else if (cellnames.includes(item)) brokenStyle = cell[item]; if (brokenStyle) { myentity.state.strokeStyle = brokenStyle; engine.strokeStyle = brokenStyle.getData(myentity, layer, DRAW); } else engine.strokeStyle = item; } else engine.strokeStyle = item.getData(myentity, layer, DRAW); }, direction: function (item, engine) { engine.direction = item; }, fontKerning: function (item, engine) { engine.fontKerning = item; }, fontStretch: function (item, engine) { engine.fontStretch = item; }, fontVariantCaps: function (item, engine) { engine.fontVariantCaps = item; }, letterSpacing: function (item, engine) { engine.letterSpacing = item; }, textAlign: function (item, engine) { engine.textAlign = LEFT; }, textBaseline: function (item, engine) { engine.textBaseline = TOP; }, textRendering: function (item, engine) { engine.textRendering = item; }, wordSpacing: function (item, engine) { engine.wordSpacing = item; }, }; // The following functions are used as part of entity object `stamp` functionality - specifically for those with a __method__ whose appearance is affected by shadows, and for the `clear` method // + Scrawl-canvas, for the most part, avoids using engine.save() and engine.restore() functionality, instead preferring to keep track of engine state in State objects. // + When clearing and restoring shadows - a frequent operation given Scrawl-canvas functionality around stamping methods - both the Cell's state object and the Canvas context engine need to be updated with the necessary data // + The same reasoning holds when setting up the context engine to 'clear' an entity from the canvas display instead of stamping it onto the canvas // `clearShadow` P.clearShadow = function () { this.engine.shadowOffsetX = 0; this.engine.shadowOffsetY = 0; this.engine.shadowBlur = 0; this.state.shadowOffsetX = 0; this.state.shadowOffsetY = 0; this.state.shadowBlur = 0; return this; }; // `restoreShadow` P.restoreShadow = function (myentity) { const state = myentity.state; this.engine.shadowOffsetX = state.shadowOffsetX; this.engine.shadowOffsetY = state.shadowOffsetY; this.engine.shadowBlur = state.shadowBlur; this.state.shadowOffsetX = state.shadowOffsetX; this.state.shadowOffsetY = state.shadowOffsetY; this.state.shadowBlur = state.shadowBlur; return this; }; // `setToClearShape` P.setToClearShape = function () { this.engine.fillStyle = BLANK; this.engine.strokeStyle = BLANK; this.engine.shadowColor = BLANK; this.state.fillStyle = BLANK; this.state.strokeStyle = BLANK; this.state.shadowColor = BLANK; return this; }; // `saveEngine`, `restoreEngine` - save and restore the Cell wrapper's &lt;canvas> element's CanvasRenderingContext2D engine's current state to/from a stack P.saveEngine = function () { this.engine.save(); return this; }; P.restoreEngine = function () { this.engine.restore(); return this; }; // `getEntityHits` - Returns an array of entity Objects responding 'true' to a checkHit call on them, for the Cell's current `.here` attribute coordinates. Used in particular with `Canvas.cascadeEventAction()` function P.getEntityHits = function () { const response = [], res = requestArray(), names = requestArray(); if (this.groupBuckets) { this.groupBuckets.forEach(g => { if (g.visibility) { res.push(...g.getAllArtefactsAt(this.here)); } }, this); } res.forEach(item => { const art = item.artefact; if (art.visibility && !names.includes(art.name)) { names.push(art.name); response.push(art); } }); releaseArray(names, res); return response; }; // `rotateDestination` - internal function, called by entity objects about to stamp themselves onto the Cell. // + entity stamp functionality works by performing a `setTransform` action on the Cell engine so that engine coordinates [0, 0] equal the entity's `currentStampPosition` coordinates, alongside any directionality (`flipReverse`, `flipUpend`) and rotational (`roll`) changes necessary // + doing it this way saves a massive amount of calculation that is otherwise required to correctly position the entity in the display P.rotateDestination = function (engine, x, y, myentity) { const self = (myentity) ? myentity : this, mimic = self.mimic, pivot = self.pivot; let reverse, upend, rotation = self.currentRotation; if (mimic && mimic.name && self.useMimicFlip) { reverse = (mimic.flipReverse) ? -1 : 1; upend = (mimic.flipUpend) ? -1 : 1; } else { reverse = (self.flipReverse) ? -1 : 1; upend = (self.flipUpend) ? -1 : 1; } if (mimic && mimic.name && self.useMimicRotation) { rotation = mimic.currentRotation; } else if (pivot && pivot.name && self.addPivotRotation) { rotation = pivot.currentRotation; } if (rotation) { rotation *= _radian; const cos = _cos(rotation), sin = _sin(rotation); engine.setTransform((cos * reverse), (sin * reverse), (-sin * upend), (cos * upend), x, y); } else engine.setTransform(reverse, 0, 0, upend, x, y); return this; }; }