- Jump To … +
  ./source/core/animation-loop.js ./source/core/display-cycle.js ./source/core/document-root-elements.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/random-seed.js ./source/core/shared-vars.js ./source/core/snippets.js ./source/core/system-flags.js ./source/core/user-interaction.js ./source/core/utilities.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/array-pool.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/canvas.js ./source/factory/cell-fragment.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conic-gradient.js ./source/factory/coordinate.js ./source/factory/crescent.js ./source/factory/drag-zone.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/filter-engine-bluenoise-data.js ./source/factory/filter-engine.js ./source/factory/filter.js ./source/factory/font-attributes.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/image-asset.js ./source/factory/keyboard-zone.js ./source/factory/line-spiral.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/noise-asset.js ./source/factory/observe-update.js ./source/factory/oval.js ./source/factory/palette.js ./source/factory/particle-force.js ./source/factory/particle-history.js ./source/factory/particle-spring.js ./source/factory/particle-world.js ./source/factory/particle.js ./source/factory/pattern.js ./source/factory/phrase.js ./source/factory/picture.js ./source/factory/polygon.js ./source/factory/polyline.js ./source/factory/quadratic.js ./source/factory/quaternion.js ./source/factory/radial-gradient.js ./source/factory/raw-asset.js ./source/factory/reaction-diffusion-asset.js ./source/factory/rectangle.js ./source/factory/render-animation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/sprite-asset.js ./source/factory/stack.js ./source/factory/star.js ./source/factory/state.js ./source/factory/tetragon.js ./source/factory/ticker.js ./source/factory/tracer.js ./source/factory/tween.js ./source/factory/unstacked-element.js ./source/factory/vector.js ./source/factory/video-asset.js ./source/factory/wheel.js ./source/mixin/anchor.js ./source/mixin/asset-advanced-functionality.js ./source/mixin/asset-consumer.js ./source/mixin/asset.js ./source/mixin/base.js ./source/mixin/cascade.js ./source/mixin/cell-key-functions.js ./source/mixin/delta.js ./source/mixin/display-shape.js ./source/mixin/dom.js ./source/mixin/entity.js ./source/mixin/filter.js ./source/mixin/mimic.js ./source/mixin/path.js ./source/mixin/pattern.js ./source/mixin/pivot.js ./source/mixin/position.js ./source/mixin/shape-basic.js ./source/mixin/shape-curve.js ./source/mixin/shape-path-calculation.js ./source/mixin/styles.js ./source/mixin/tween.js

- §

  # Grid factory

  The Grid entity is a graphical representation of a grid with equal width columns and equal height rows, separated by gutters. Each tile within the grid can be filled with a different color, or a different gradient, or a different Picture entity output.

  - Each grid requires a mimimum of one column and one row; columns will divide into the Grid width evenly, rows divide evenly into the Grid height.
  - The tiles can display as many different colors, or gradients, or Pictures, as are required for the display.
  - The gutters between columns, and between rows, can be different values; gutter fills can also be colors, gradients or Picture entity output.
  - The display for each tile, and the gutters, can be easily updated.
  - Grid entity hit reports include an extra attribute - an array of tiles that report being hit.
  - Grids will accept Filter objects - filters are applied to the entire Grid, not to individual tiles.
  - They can use Anchor objects for user navigation.
  - They can be rendered to the canvas by including them in a Cell object’s Group.
  - They can be animated directly, or using delta animation, or act as the target for Tween animations.
  - Grids can be cloned, and killed.
- §

  #### Demos:

  - Canvas-022 - Grid entity - basic functionality (color, gradients)
  - Canvas-023 - Grid entity - using picture-based assets (image, video, sprite)
  - Packets-002 - Scrawl-canvas packets - save and load a range of different entitys
- §

  #### Imports

      import { constructors, entity } from '../core/library.js';

      import { doCreate, isa_number, isa_obj, mergeOver, pushUnique, xt, xta, λnull, Ωempty } from '../core/utilities.js';

      import { releaseCell, requestCell } from './cell-fragment.js';

      import baseMix from '../mixin/base.js';
      import entityMix from '../mixin/entity.js';

      import { _isArray, _isInteger, _parse, BLACK, CELL_GRADIENT, COLOR, ENTITY, FILL, GRAY, GRID_GRADIENT, GRID_PICTURE, SOURCE_IN, SOURCE_OVER, T_GRID, TILE_PICTURE, WHITE } from '../core/shared-vars.js';

- §

  #### Grid constructor

      const Grid = function (items = Ωempty) {

          this.tileFill = [];
          this.tileSources = [];

          this.entityInit(items);

          if (!items.tileSources) {

              this.tileSources = [].concat([{

                  type: COLOR,
                  source: BLACK,
              },
              {

                  type: COLOR,
                  source: WHITE,
              }]);
          }

          if (!items.tileFill) {

              this.tileFill.length = this.columns * this.rows;
              this.tileFill.fill(0);
          }
          else if (_isArray(items.tileFill) && this.tileFill.length === items.tileFill.length) {

              this.tileFill = items.tileFill;
          }

          this.tilePaths = [];
          this.tileRealCoordinates = [];
          this.tileVirtualCoordinates = [];

          if (!items.dimensions) {

              if (!items.width) this.currentDimensions[0] = this.dimensions[0] = 20;
              if (!items.height) this.currentDimensions[1] = this.dimensions[1] = 20;
          }

          return this;
      };

- §

  #### Block prototype

      const P = Grid.prototype = doCreate();
      P.type = T_GRID;
      P.lib = ENTITY;
      P.isArtefact = true;
      P.isAsset = false;

- §

  #### Mixins

  - base
  - entity

      baseMix(P);
      entityMix(P);

- §

  #### Grid attributes

  - Attributes defined in the base mixin: name.
  - Attributes defined in the position mixin: group, visibility, order, calculateOrder, stampOrder, start, startX, startY, handle, handleX, handleY, offset, offsetX, offsetY, dimensions, width, height, pivoted, mimicked, lockTo, lockXTo, lockYTo, scale, roll, noUserInteraction, noPositionDependencies, noCanvasEngineUpdates, noFilters, noPathUpdates, purge, bringToFrontOnDrag.
  - Attributes defined in the delta mixin: delta, noDeltaUpdates.
  - Attributes defined in the pivot mixin: pivot, pivotCorner, addPivotHandle, addPivotOffset, addPivotRotation.
  - Attributes defined in the mimic mixin: mimic, useMimicDimensions, useMimicScale, useMimicStart, useMimicHandle, useMimicOffset, useMimicRotation, useMimicFlip, addOwnDimensionsToMimic, addOwnScaleToMimic, addOwnStartToMimic, addOwnHandleToMimic, addOwnOffsetToMimic, addOwnRotationToMimic.
  - Attributes defined in the path mixin: path, pathPosition, addPathHandle, addPathOffset, addPathRotation, constantPathSpeed.
  - Attributes defined in the entity mixin: method, pathObject, winding, flipReverse, flipUpend, scaleOutline, lockFillStyleToEntity, lockStrokeStyleToEntity, onEnter, onLeave, onDown, onUp, fillStyle, strokeStyle, globalAlpha, globalCompositeOperation, lineWidth, lineCap, lineJoin, lineDash, lineDashOffset, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, filter.
  - Attributes defined in the anchor mixin: anchor.
  - Attributes defined in the filter mixin: filters, isStencil.

      const defaultAttributes = {

- §

  columns, rows - integer Numbers representing the number of columns and rows in the Grid

          columns: 2,
          rows: 2,

- §

  columnGutterWidth, rowGutterWidth - float Number distances (measured in px) between tiles in the grid.

          columnGutterWidth: 1,
          rowGutterWidth: 1,

- §

  tileSources - Array of Javascript Objects

  - Each Object describes a source which can be used to fill tiles
  - Available fills include: `color`, `cellGradient`, `gridGradient`, `gridPicture`, `tilePicture`

          tileSources: null,

- §

  tileFill - Array of integer Numbers

  - Length of the Array will be `rows * columns`
  - Tiles are arranged left-to-right, top-to-bottom with tileFill[0] being the top left tile in the grid
  - Each Number represents the index of the tileSource object to be used to fill this tile

          tileFill: null,

- §

  gutterColor - can accept the following sources:

  - A valid CSS color String
  - The name-String of a Scrawl-canvas Gradient or RadialGradient object, or the object itself
  - An integer Number representing the index of a tileSource object

          gutterColor: GRAY,
      };
      P.defs = mergeOver(P.defs, defaultAttributes);

- §

  #### Packet management

      P.packetExclusions = pushUnique(P.packetExclusions, ['tileSources']);

      P.finalizePacketOut = function (copy, items) {

          const cSources = copy.tileSources = [],
              tSources = this.tileSources;

          tSources.forEach(item => {

              cSources.push({
                  type: item.type,
                  source: (isa_obj(item.source)) ? item.source.name : item.source
              });
          });

          if (isa_obj(copy.gutterColor)) copy.gutterColor = copy.gutterColor.name;

          const stateCopy = _parse(this.state.saveAsPacket(items))[3];
          copy = mergeOver(copy, stateCopy);

          copy = this.handlePacketAnchor(copy, items);

          return copy;
      };

- §

  #### Clone management

  No additional clone functionality required

- §

  #### Kill management

  No additional kill functionality required

- §

  #### Get, Set, deltaSet

      const S = P.setters,
          D = P.deltaSetters;

- §

  columns

      S.columns = function (item) {

          if (isa_number(item)) {

              if (!_isInteger(item)) item = parseInt(item, 10);

              if (item != this.columns) {

                  let i, iz, j;

                  const currentFill = this.tileFill,
                      currentCols = this.columns,
                      newFill = [];

                  this.columns = item;

                  for (i = 0, iz = this.rows; i < iz; i++) {

                      for (j = 0; j < item; j++) {

                          if (j < currentCols) newFill.push(currentFill[(i * currentCols) + j]);
                          else newFill.push(0);
                      }
                  }
                  this.tileFill = newFill;
              }
          }
          this.dirtyPathObject = true;
          this.dirtyFilterIdentifier = true;
      };
      D.columns = λnull;

- §

  rows

      S.rows = function (item) {

          if (isa_number(item)) {

              if (!_isInteger(item)) item = parseInt(item, 10);

              if (item != this.rows) {

                  const currentRows = this.rows;

                  this.rows = item;

                  this.tileFill.length = this.columns * item;

                  if (currentRows < item) this.tileFill.fill(0, currentRows * this.columns);
              }
          }
          this.dirtyPathObject = true;
          this.dirtyFilterIdentifier = true;
      };
      D.rows = λnull;

- §

  #### Tile management

- §

  `setAllTilesTo` - change the fill for all tiles in a Grid

  - Argument is an integer Number representing the index of a tileSource object

      P.setAllTilesTo = function (val) {

          if (isa_number(val)) {

              if (!_isInteger(val)) val = parseInt(val, 10);

              this.tileFill.fill(val);
              this.dirtyFilterIdentifier = true;
          }
          return this;
      };

- §

  `setTileFill` - update the tileFill array

  - The array supplied as an argument must be the same length as current rows * columns

      P.setTileFill = function (item) {

          const { columns, rows } = this;

          if (_isArray(item) && item.length == columns * rows) {

              this.tileFill = item;
              this.dirtyFilterIdentifier = true;
          }
          return this;
      };

- §

  `setTilesTo` - change the fill for a (set of) tile(s) in a Grid - requires two arguments:

  - First argument is an integer Number representing the position of a tile in the tileFill Array, or an Array of such Numbers
  - Second argument is an integer Number representing the index of a tileSource object

      P.setTilesTo = function (tiles, val) {

          const tileFill = this.tileFill;

          if (xt(tiles) && isa_number(val)) {

              if (!_isInteger(val)) val = parseInt(val, 10);

              if (isa_number(tiles)) tileFill[tiles] = val;
              else if (_isArray(tiles)) {

                  tiles.forEach(tile => {

                      if (isa_number(tile)) tileFill[tile] = val;
                  });
              }
              this.dirtyFilterIdentifier = true;
          }
          return this;
      };

- §

  `setTileSourceTo` - update or replace a tileSource object - requires two arguments:

  - First argument is the integer Number index of the tileSource object to be replaced
  - Second argument is the new or updated object

      P.setTileSourceTo = function (index, obj) {

          if (isa_number(index) && isa_obj(obj)) {

              if (obj.type && obj.source) this.tileSources[index] = obj;
          }
          return this;
      };

- §

  `removeTileSource` - remove a tileSource object

  - Argument is an integer Number representing the index of a tileSource object
  - Object will be replaced with `null`

      P.removeTileSource = function (index) {

          if (isa_number(index) && index) {

              this.tileSources[index] = null;

              this.tileFill = this.tileFill.map(item => item === index ? 0 : item);
          }
          return this;
      };

- §

  `getTileSource` - returns the tileSource index Number for the given tile. Function is overloaded:

  - One argument: Number representing the position of a tile in the tileFill Array.
  - Two arguments: The `(row, column)` position of the tile in the Grid - both values start from `0`

      P.getTileSource = function (row, col) {

          if (isa_number(row)) {

              if (!isa_number(col)) return this.tileFill[row];
              else return this.tileFill[(row * this.rows) + col];
          }
      };

- §

  `getTilesUsingSource` - returns an Array of tileFill index Numbers representing tiles that are currently using the tileSource Object at the given tileSource index.

      P.getTilesUsingSource = function (key) {

          const res = [];

          if (isa_number(key)) this.tileFill.forEach((val, index) => val == key && res.push(index));

          return res;
      };

- §

  `cleanPathObject` - internal - used for entity stamping (Display cycle), and collision detection

      P.cleanPathObject = function () {

          this.dirtyPathObject = false;

          if (!this.noPathUpdates || !this.pathObject) {

              const p = this.pathObject = new Path2D(),
                  rowLines = new Path2D(),
                  colLines = new Path2D();

              const handle = this.currentStampHandlePosition,
                  scale = this.currentScale,
                  dims = this.currentDimensions;

              const x = -handle[0] * scale,
                  y = -handle[1] * scale,
                  w = dims[0] * scale,
                  h = dims[1] * scale;

              p.rect(x, y, w, h);

              const cols = this.columns,
                  rows = this.rows,
                  colWidth = w / cols,
                  rowHeight = h / rows,
                  paths = this.tilePaths,
                  real = this.tileRealCoordinates,
                  virtual = this.tileVirtualCoordinates;

              let i, j, cx, cy;

              rowLines.moveTo(x, y);
              rowLines.lineTo(x + w, y);

              for (i = 1; i <= rows; i++) {

                  const ry = y + (i * rowHeight);

                  rowLines.moveTo(x, ry);
                  rowLines.lineTo(x + w, ry);
              }
              this.rowLines = rowLines;

              colLines.moveTo(x, y);
              colLines.lineTo(x, y + h);

              for (j = 1; j <= cols; j++) {

                  cx = x + (j * colWidth);

                  colLines.moveTo(cx, y);
                  colLines.lineTo(cx, y + h);
              }
              this.columnLines = colLines;

              paths.length = 0;
              real.length = 0;
              virtual.length = 0;

              for (i = 0; i < rows; i++) {

                  for (j = 0; j < cols; j++) {

                      const path = new Path2D();

                      cx = j * colWidth;
                      cy = i * rowHeight;

                      path.rect(x + cx, y + cy, colWidth, rowHeight);
                      paths.push(path);

                      virtual.push([cx, cy]);
                      real.push([x + cx, y + cy]);
                  }
              }

              this.currentTileWidth = colWidth;
              this.currentTileHeight = rowHeight;
          }
      };

- §

  ##### Stamp methods

- §

  `performFill` - internal stamp method helper function

  - If you are not a fan of long, complex functions … look away now!

      P.performFill = function (engine) {

- §

  Grab the current engine values for various things

          engine.save();

          const composer = requestCell(),
              compEngine = composer.engine,
              compCanvas = composer.element;

          const tileSources = this.tileSources,
              tileFill = this.tileFill,
              tilePaths = this.tilePaths,
              tileRealCoords = this.tileRealCoordinates,
              tileVirtualCoords = this.tileVirtualCoordinates,
              winding = this.winding,
              tileWidth = this.currentTileWidth,
              tileHeight = this.currentTileHeight,
              scale = this.scale;

          const dims = this.currentDimensions;

          let currentPicture;

- §

  Iterate through the grid’s tileSources

          tileSources.forEach((obj, index) => {

- §

  Set up the engine fillStyle value (where required)

              if (obj && obj.type) {

                  switch (obj.type) {

                      case COLOR :

                          engine.fillStyle = obj.source;
                          break;

                      case CELL_GRADIENT :

                          this.lockFillStyleToEntity = false;
                          engine.fillStyle = obj.source.getData(this, this.currentHost);
                          break;

                      case GRID_GRADIENT :

                          this.lockFillStyleToEntity = true;
                          engine.fillStyle = obj.source.getData(this, this.currentHost);
                          break;
                  }
              }

- §

  Get an map of tiles using this source

              const validTiles = tileFill.map(item => item === index ? true : false);

              if (validTiles.length) {

                  switch (obj.type) {

- §

  Use pool canvas to compose the output

                      case GRID_PICTURE :

                          currentPicture = (obj.source.substring) ? entity[obj.source] : obj.source;

                          if (currentPicture.simpleStamp) {

                              compCanvas.width = dims[0] * scale;
                              compCanvas.height = dims[1] * scale;
                              compEngine.globalCompositeOperation = SOURCE_OVER;
                              compEngine.fillStyle = BLACK;

                              validTiles.forEach((tile, pos) => {

                                  if (tile) compEngine.fillRect(tileVirtualCoords[pos][0], tileVirtualCoords[pos][1], tileWidth, tileHeight);
                              });

                              compEngine.globalCompositeOperation = SOURCE_IN;

                              currentPicture.simpleStamp(composer, {
                                  startX: 0,
                                  startY: 0,
                                  width: dims[0] * scale,
                                  height: dims[1] * scale,
                                  method: FILL,
                              });

                              engine.drawImage(compCanvas, ~~tileRealCoords[0][0], ~~tileRealCoords[0][1]);
                          }
                          break;

                      case TILE_PICTURE :

                          currentPicture = (obj.source.substring) ? entity[obj.source] : obj.source;

                          if (currentPicture.simpleStamp) {

                              compCanvas.width = tileWidth;
                              compCanvas.height = tileHeight;
                              compEngine.globalCompositeOperation = SOURCE_OVER;

                              currentPicture.simpleStamp(composer, {
                                  startX: 0,
                                  startY: 0,
                                  width: tileWidth,
                                  height: tileHeight,
                                  method: FILL,
                              });

                              validTiles.forEach((tile, pos) => tile && engine.drawImage(compCanvas, ~~tileRealCoords[pos][0], ~~tileRealCoords[pos][1]));
                          }
                          break;

                      default :

                          validTiles.forEach((tile, pos) => tile && engine.fill(tilePaths[pos], winding));
                  }
              }
          });

          const gColor = this.gutterColor,
              gRow = this.rowGutterWidth,
              gCol = this.columnGutterWidth;

          let gObject;

          if(xt(gColor)) {

- §

  Assign (or construct) the appropriate object to gObject

              if (gColor.substring) {

                  gObject = {
                      type: COLOR,
                      source: this.gutterColor
                  };
              }
              else if (isa_obj(gColor)) gObject = gColor;
              else if (isa_number(gColor) && isa_obj(tileSources[gColor])) gObject = tileSources[gColor];

- §

  Set the engine’s strokeStyle to the appropriate value (if needed)

              switch (gObject.type) {

                  case CELL_GRADIENT :

                      this.lockFillStyleToEntity = false;
                      engine.strokeStyle = gObject.source.getData(this, this.currentHost);
                      break;

                  case GRID_GRADIENT :

                      this.lockFillStyleToEntity = true;
                      engine.strokeStyle = gObject.source.getData(this, this.currentHost);
                      break;

                  case COLOR :

                      engine.strokeStyle = gObject.source;
                      break;
              }

              switch (gObject.type) {

- §

  Use pool canvas to compose the output

  - gridPicture and tilePicture both treated the same

                  case GRID_PICTURE :
                  case TILE_PICTURE :

                      if(gRow || gCol) {

                          currentPicture = (gObject.source.substring) ? entity[gObject.source] : gObject.source;

                          if (currentPicture.simpleStamp) {

                              const handle = this.currentStampHandlePosition,
                                  scale = this.currentScale,
                                  x = handle[0] * scale,
                                  y = handle[1] * scale;

                              compCanvas.width = dims[0] * scale;
                              compCanvas.height = dims[1] * scale;
                              compEngine.globalCompositeOperation = SOURCE_OVER;
                              compEngine.strokeStyle = BLACK;
                              compEngine.translate(x, y);

                              if (gRow) {

                                  compEngine.lineWidth = gRow;
                                  compEngine.stroke(this.rowLines);
                              }

                              if (gCol) {

                                  compEngine.lineWidth = gCol;
                                  compEngine.stroke(this.columnLines);
                              }

                              compEngine.globalCompositeOperation = SOURCE_IN;

                              currentPicture.simpleStamp(composer, {
                                  startX: 0,
                                  startY: 0,
                                  width: dims[0] * scale,
                                  height: dims[1] * scale,
                                  method: FILL,
                              });

                              engine.drawImage(compCanvas, ~~tileRealCoords[0][0], ~~tileRealCoords[0][1]);
                              compEngine.translate(0, 0);
                          }
                      }
                      break;

- §

  We have a color/gradient all set up - stroke the lines directly onto grid

                  default :

                      if (gRow) {

                          engine.lineWidth = gRow;
                          engine.stroke(this.rowLines);
                      }

                      if (gCol) {

                          engine.lineWidth = gCol;
                          engine.stroke(this.columnLines);
                      }
              }
          }

          releaseCell(composer);

          engine.restore();
      };

- §

  `fill`

      P.fill = function (engine) {

          this.performFill(engine);
      };

- §

  `drawAndFill`

      P.drawAndFill = function (engine) {

          const p = this.pathObject;

          engine.stroke(p);
          this.currentHost.clearShadow();
          this.performFill(engine);
      };

- §

  `fillAndDraw`

      P.fillAndDraw = function (engine) {

          const p = this.pathObject;

          engine.stroke(p);
          this.currentHost.clearShadow();
          this.performFill(engine);
          engine.stroke(p);
      };

- §

  `drawThenFill`

      P.drawThenFill = function (engine) {

          const p = this.pathObject;

          engine.stroke(p);
          this.performFill(engine);
      };

- §

  `fillThenDraw`

      P.fillThenDraw = function (engine) {

          const p = this.pathObject;

          this.performFill(engine);
          engine.stroke(p);
      };

- §

  `checkHit` - overrides position mixin function

  - Grid entitys need to return ALL of the successful hit coordinates, not just the first
  - They also need to include the tile index(es) of where the hit(s) took place within them

  Returns an object with the following attributes

      {
          x: x-coordinate of the _last_ successful hit,
          y: y-coordinate of the _last_ successful hit,
          tiles: Array of tile index Numbers representing each tile reporting a hit,
          artefact: the Grid entity object
      }

      P.checkHit = function (items = [], mycell) {

          if (this.noUserInteraction) return false;

          if (!this.pathObject || this.dirtyPathObject) {

              this.cleanPathObject();
          }

          const tests = (!_isArray(items)) ?  [items] : items;

          let poolCellFlag = false;

          if (!mycell) {

              mycell = requestCell();
              poolCellFlag = true;
          }

          const engine = mycell.engine,
              stamp = this.currentStampPosition,
              x = stamp[0],
              y = stamp[1],
              tiles = new Set(),
              tilePaths = this.tilePaths;

          let isGood, tx, ty;

          const getCoords = (coords) => {

              let x, y;

              if (_isArray(coords)) {

                  x = coords[0];
                  y = coords[1];
              }
              else if (xta(coords, coords.x, coords.y)) {

                  x = coords.x;
                  y = coords.y;
              }
              else return [false];

              if (!x.toFixed || !y.toFixed || isNaN(x) || isNaN(y)) return [false];

              return [true, x, y];
          }

          mycell.rotateDestination(engine, x, y, this);

          if (tests.some(test => {

              [isGood, tx, ty] = getCoords(test);

              if (!isGood) return false;
              else return engine.isPointInPath(this.pathObject, tx, ty, this.winding);

          }, this)) {

              tests.forEach(test => {

                  [isGood, tx, ty] = getCoords(test);

                  if (isGood) {

                      tilePaths.some((path, index) => {

                          if (engine.isPointInPath(path, tx, ty, this.winding)) {

                              tiles.add(index);
                              return true;
                          }
                          return false;
                      })
                  }
              })

              if (poolCellFlag) releaseCell(mycell);

              return {
                  x: tx,
                  y: ty,
                  tiles: [...tiles],
                  artefact: this
              };
          }

          if (poolCellFlag) releaseCell(mycell);

          return false;
      };

- §

  #### Factory

      let blueSource = {
          type: 'color',
          source: 'aliceblue',
      };

      let myGrid = scrawl.makeGrid({

          name: 'test-grid',

          startX: 'center',
          startY: 'center',

          handleX: 'center',
          handleY: 'center',

          width: 300,
          height: 200,

          columns: 6,
          rows: 6,

          tileSources: [blueSource, {
              type: 'color',
              source: 'red',
          }],
      });

      export const makeGrid = function (items) {

          if (!items) return false;
          return new Grid(items);
      };

      constructors.Grid = Grid;