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

296 lines (206 loc) 7.66 kB
// # Oval factory // A factory for generating oval shape-based entitys // #### Imports import { constructors } from '../core/library.js'; import { addStrings, doCreate, mergeOver, Ωempty } from '../helper/utilities.js'; import baseMix from '../mixin/base.js'; import shapeMix from '../mixin/shape-basic.js'; // Shared constants import { ENTITY, RADIUS_X, RADIUS_XY, RADIUS_Y, ZERO_PATH } from '../helper/shared-vars.js'; // Local constants const OVAL = 'oval', T_OVAL = 'Oval'; // #### Oval constructor const Oval = function (items = Ωempty) { this.shapeInit(items); return this; }; // #### Oval prototype const P = Oval.prototype = doCreate(); P.type = T_OVAL; P.lib = ENTITY; P.isArtefact = true; P.isAsset = false; // #### Mixins baseMix(P); shapeMix(P); // #### Oval attributes const defaultAttributes = { radiusX: 5, radiusY: 5, intersectX: 0.5, intersectY: 0.5, offshootA: 0.55, offshootB: 0, }; 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 S = P.setters, D = P.deltaSetters; S.radius = function (item) { this.setRectHelper(item, RADIUS_XY); }; S.radiusX = function (item) { this.setRectHelper(item, RADIUS_X); }; S.radiusY = function (item) { this.setRectHelper(item, RADIUS_Y); }; D.radius = function (item) { this.deltaRectHelper(item, RADIUS_XY); }; D.radiusX = function (item) { this.deltaRectHelper(item, RADIUS_X); }; D.radiusY = function (item) { this.deltaRectHelper(item, RADIUS_Y); }; S.offshootA = function (item) { if (item.toFixed) { this.offshootA = item; this.updateDirty(); } }; S.offshootB = function (item) { if (item.toFixed) { this.offshootB = item; this.updateDirty(); } }; D.offshootA = function (item) { if (item.toFixed) { this.offshootA += item; this.updateDirty(); } }; D.offshootB = function (item) { if (item.toFixed) { this.offshootB += item; this.updateDirty(); } }; S.intersectX = function (item) { if (item.toFixed) { this.intersectX = item; this.updateDirty(); } }; S.intersectY = function (item) { if (item.toFixed) { this.intersectY = item; this.updateDirty(); } }; D.intersectX = function (item) { if (item.toFixed) { this.intersectX += item; this.updateDirty(); } }; D.intersectY = function (item) { if (item.toFixed) { this.intersectY += item; this.updateDirty(); } }; // #### Prototype functions // `setRectHelper` - internal setter helper function P.setRectHelper = function (item, corners) { this.updateDirty(); corners.forEach(corner => { this[corner] = item; }, this); }; // `deltaRectHelper` - internal setter helper function P.deltaRectHelper = function (item, corners) { this.updateDirty(); corners.forEach(corner => { this[corner] = addStrings(this[corner], item); }, this); }; // `cleanSpecies` - internal helper function - called by `prepareStamp` P.cleanSpecies = function () { this.dirtySpecies = false; this.pathDefinition = this.makeOvalPath(); }; // `makeOvalPath` - internal helper function - called by `cleanSpecies` P.makeOvalPath = function () { const A = parseFloat(this.offshootA.toFixed(6)), B = parseFloat(this.offshootB.toFixed(6)), radiusX = this.radiusX, radiusY = this.radiusY; let width, height; if (radiusX.substring || radiusY.substring) { const host = this.getHost(); if (host) { const [hW, hH] = host.currentDimensions; const rx = (radiusX.substring) ? (parseFloat(radiusX) / 100) * hW : radiusX, ry = (radiusY.substring) ? (parseFloat(radiusY) / 100) * hH : radiusY; width = rx * 2; height = ry * 2; } } else { width = radiusX * 2; height = radiusY * 2; } const port = parseFloat((width * this.intersectX).toFixed(2)), starboard = parseFloat((width - port).toFixed(2)), fore = parseFloat((height * this.intersectY).toFixed(2)), aft = parseFloat((height - fore).toFixed(2)); let myData = ZERO_PATH; myData += `c${starboard * A},${fore * B} ${starboard - (starboard * B)},${fore - (fore * A)}, ${starboard},${fore} `; myData += `${-starboard * B},${aft * A} ${-starboard + (starboard * A)},${aft - (aft * B)} ${-starboard},${aft} `; myData += `${-port * A},${-aft * B} ${-port + (port * B)},${-aft + (aft * A)} ${-port},${-aft} `; myData += `${port * B},${-fore * A} ${port - (port * A)},${-fore + (fore * B)} ${port},${-fore}z`; return myData; }; P.calculateLocalPathAdditionalActions = function () { let scale = this.scale; if (scale < 0.001) scale = 0.001; const [x, y] = this.localBox; this.pathDefinition = this.pathDefinition.replace(ZERO_PATH, `m${-x / scale},${-y / scale}`); this.pathCalculatedOnce = false; // ALWAYS, when invoking `calculateLocalPath` from `calculateLocalPathAdditionalActions`, include the second argument, set to `true`! Failure to do this leads to an infinite loop which will make your machine weep. // + We need to recalculate the local path to take into account the offset required to put the Oval entity's start coordinates at the top-left of the local box, and to recalculate the data used by other artefacts to place themselves on, or move along, its path. this.calculateLocalPath(this.pathDefinition, true); }; // #### Factories // ##### makeOval // Scrawl-canvas uses quadratic curves internally to create the curved path. // + The _bend_ of these curves is set by the quadratic's control point which doesn't have its own coordinate but is rather calculated using two float Number variables: __offshootA__ (default: `0.55`) and __offshootB__ (default: `0`) - change these values to make the quarter-curves more or less bendy. // + The main shape of the oval is determined by differing radius lengths in the `x` and `y` directions, as set by the attributes __radiusX__ and __radiusY__; to set both radiuses to the same value, use ____radius____ instead. // + The radius values can be: _absolute_ (using Number values); or _relative_ using %-String values - with the y radius representing a portion of the Cell container's height and the x radius the Cell's width. // + The radiuses (as diameter lines) cross in the middle of the oval shape. We can move the position of where the intersection happens by setting a float Number value between `0.0 - 1.0` (or beyond those limits) for the __intersectX__ and __intersectY__ attributes. // + `intersectX` (default: `0.5`) represents the point at which the `y` diameter crosses the `x` diameter, with `0` being the left end and `1` being the right end. // + `intersectY` (default: `0.5`) represents the point at which the `x` diameter crosses the `y` diameter, with `0` being the top end and `1` being the bottom end. // // ``` // scrawl.makeOval({ // // name: 'egg', // // fillStyle: 'lightGreen', // method: 'fillAndDraw', // // startX: 20, // startY: 20, // // radiusX: '7%', // radiusY: '3%', // // intersectY: 0.6, // }); // ``` export const makeOval = function (items) { if (!items) return false; items.species = OVAL; return new Oval(items); }; constructors.Oval = Oval;