• Jump To … +
    ./source/core/animationloop.js ./source/core/component.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/userInteraction.js ./source/core/utilities.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/canvas.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/coordinate.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/filter.js ./source/factory/fontAttributes.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/imageAsset.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/noise.js ./source/factory/oval.js ./source/factory/palette.js ./source/factory/particle.js ./source/factory/particleForce.js ./source/factory/particleHistory.js ./source/factory/particleSpring.js ./source/factory/particleWorld.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/radialGradient.js ./source/factory/rectangle.js ./source/factory/renderAnimation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/spriteAsset.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/unstackedElement.js ./source/factory/vector.js ./source/factory/videoAsset.js ./source/factory/wheel.js ./source/mixin/anchor.js ./source/mixin/asset.js ./source/mixin/assetConsumer.js ./source/mixin/base.js ./source/mixin/cascade.js ./source/mixin/delta.js ./source/mixin/displayShape.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/shapeBasic.js ./source/mixin/shapeCurve.js ./source/mixin/shapePathCalculation.js ./source/mixin/styles.js ./source/mixin/tween.js ./source/worker/filter-string.js ./source/worker/filter.js
  • ¶

    Shape-curve mixin

    This mixin defines additional attributes and functionality used byScrawl-canvas line, quadratic and bezier path-defined entitys.

    These entitys have additional positioning coordinates (startControl and endControl for Bezier entitys; control for Quadratic entitys; all three entity types use the end coordinate) which we use to construct and position them. Other artefacts can pivot themselves to these coordinates, and path-defined entitys can pivot the coordinates to other artefacts.

  • ¶

    Imports

    import { artefact, particle } from '../core/library.js';
    import { mergeOver, isa_boolean, xt, xta, addStrings, removeItem, pushUnique } from '../core/utilities.js';
    
    import { makeCoordinate } from '../factory/coordinate.js';
    
    
    const capitalize = (s) => {
    
        if (typeof s !== 'string') return '';
        return s.charAt(0).toUpperCase() + s.slice(1);
    }
  • ¶

    Export function

    export default function (P = {}) {
  • ¶

    Shared attributes

        let defaultAttributes = {
  • ¶

    The end coordinate (‘pin’) defines where a curve terminates.

    • Similar to the start coordinate, the end coordinate can be updated using the pseudo-attributes endX and endY.
            end: null,
  • ¶

    endPivot, endPivotCorner, addEndPivotHandle, addEndPivotOffset

    • Like the start coordinate, the end coordinate can be pivoted to another artefact. These attributes are used in the same way as the pivot, ‘pivotCorner’, addPivotHandle and addPivotOffset attributes.
            endPivot: '',
            endPivotCorner: '',
            addEndPivotHandle: false,
            addEndPivotOffset: false,
  • ¶

    endPath, endPathPosition, addEndPathHandle, addEndPathOffset

    • Like the start coordinate, the end coordinate can be pathed to another artefact. These attributes are used in the same way as the path, ‘pathPosition’, addPathHandle and addPathOffset attributes.
            endPath: '',
            endPathPosition: 0,
            addEndPathHandle: false,
            addEndPathOffset: true,
  • ¶

    endParticle - attribute to store any particle the artefact mey be using for its position reference

            endParticle: '',
  • ¶

    endLockTo

    • Like the start coordinate, the end coordinate can swap between using absolute and relative positioning by setting this attribute. Accepted values are: coord (default, for absolute positioning), pivot, path, position, mouse.
    • The end coordinate does not support ‘mimic’ relative positioning.
    • The end lock does not support setting the x and y coordinates separately - its value is a string argument, not an [x, y] array!
            endLockTo: '',
  • ¶

    useStartAsControlPoint - by default, updating the curve entity’s start coordinate will move the entire curve. In situations where we need to treat the start coordinate like a curve ‘pin’, set this attribute to true.

            useStartAsControlPoint: false,
        };
        P.defs = mergeOver(P.defs, defaultAttributes);
  • ¶

    Packet management

        P.packetExclusions = pushUnique(P.packetExclusions, ['controlledLineOffset']);
        P.packetExclusionsByRegex = pushUnique(P.packetExclusionsByRegex, []);
        P.packetCoordinates = pushUnique(P.packetCoordinates, ['end']);
        P.packetObjects = pushUnique(P.packetObjects, ['endPivot', 'endPath']);
        P.packetFunctions = pushUnique(P.packetFunctions, []);
  • ¶

    Clone management

    No additional clone functionality defined here

  • ¶

    Kill management

    P.factoryKill = function () {
    
        Object.entries(artefact).forEach(([name, art]) => {
    
            if (art.name !== this.name) {
    
                if (art.startControlPivot && art.startControlPivot.name === this.name) art.set({ startControlPivot: false});
                if (art.controlPivot && art.controlPivot.name === this.name) art.set({ controlPivot: false});
                if (art.endControlPivot && art.endControlPivot.name === this.name) art.set({ endControlPivot: false});
                if (art.endPivot && art.endPivot.name === this.name) art.set({ endPivot: false});
    
                if (art.startControlPath && art.startControlPath.name === this.name) art.set({ startControlPath: false});
                if (art.controlPath && art.controlPath.name === this.name) art.set({ controlPath: false});
                if (art.endControlPath && art.endControlPath.name === this.name) art.set({ endControlPath: false});
                if (art.endPath && art.endPath.name === this.name) art.set({ endPath: false});
            }
        });
    };
  • ¶

    Get, Set, deltaSet

        let G = P.getters,
            S = P.setters,
            D = P.deltaSetters;
  • ¶

    useStartAsControlPoint

        S.useStartAsControlPoint = function (item) {
    
            this.useStartAsControlPoint = item;
    
            if (!item) {
    
                let controlledLine = this.controlledLineOffset;
                controlledLine[0] = 0;
                controlledLine[1] = 0;
            }
    
            this.updateDirty();
        };
  • ¶

    endPivot

        S.endPivot = function (item) {
    
            this.setControlHelper(item, 'endPivot', 'end');
            this.updateDirty();
            this.dirtyEnd = true;
        };
  • ¶

    endParticle

        S.endParticle = function (item) {
    
            this.setControlHelper(item, 'endParticle', 'end');
            this.updateDirty();
            this.dirtyEnd = true;
        };
  • ¶

    endPath

        S.endPath = function (item) {
    
            this.setControlHelper(item, 'endPath', 'end');
            this.updateDirty();
            this.dirtyEnd = true;
        };
  • ¶

    endPathPosition

        S.endPathPosition = function (item) {
    
            this.endPathPosition = item;
            this.dirtyEnd = true;
            this.currentEndPathData = false;
        };
        D.endPathPosition = function (item) {
    
            this.endPathPosition += item;
            this.dirtyEnd = true;
            this.currentEndPathData = false;
        };
  • ¶

    end

    • pseudo-attributes endX, endY
        S.endX = function (coord) {
    
            if (coord != null) {
    
                this.end[0] = coord;
                this.updateDirty();
                this.dirtyEnd = true;
                this.currentEndPathData = false;
            }
        };
        S.endY = function (coord) {
    
            if (coord != null) {
    
                this.end[1] = coord;
                this.updateDirty();
                this.dirtyEnd = true;
                this.currentEndPathData = false;
            }
        };
        S.end = function (x, y) {
    
            this.setCoordinateHelper('end', x, y);
            this.updateDirty();
            this.dirtyEnd = true;
            this.currentEndPathData = false;
        };
        D.endX = function (coord) {
    
            let c = this.end;
            c[0] = addStrings(c[0], coord);
            this.updateDirty();
            this.dirtyEnd = true;
            this.currentEndPathData = false;
        };
        D.endY = function (coord) {
    
            let c = this.end;
            c[1] = addStrings(c[1], coord);
            this.updateDirty();
            this.dirtyEnd = true;
            this.currentEndPathData = false;
        };
        D.end = function (x, y) {
    
            this.setDeltaCoordinateHelper('end', x, y);
            this.updateDirty();
            this.dirtyEnd = true;
            this.currentEndPathData = false;
        };
  • ¶

    endLockTo

        S.endLockTo = function (item) {
    
            this.endLockTo = item;
            this.updateDirty();
            this.dirtyEndLock = true;
            this.currentEndPathData = false;
        };
  • ¶

    Prototype functions

  • ¶

    curveInit - internal constructor helper function

        P.curveInit = function (items) {
    
            this.end = makeCoordinate();
            this.currentEnd = makeCoordinate();
            this.endLockTo = 'coord';
            this.dirtyEnd = true;
    
            this.dirtyPins = [];
    
            this.controlledLineOffset = makeCoordinate();
        };
  • ¶

    setControlHelper - internal setter helper function

        P.setControlHelper = function (item, attr, label) {
    
            if (isa_boolean(item) && !item) {
    
                this[attr] = null;
    
                if (label === 'startControl') this.dirtyStartControlLock = true;
                else if (label === 'control') this.dirtyControlLock = true;
                else if (label === 'endControl') this.dirtyEndControlLock = true;
                else this.dirtyEndLock = true;
            }
            else if (item) {
    
                let oldControl = this[attr],
                    newControl = (item.substring) ? artefact[item] : item;
    
                if (attr.indexOf('Pivot') > 0) {
                    
                    if (newControl && newControl.isArtefact) {
    
                        if (oldControl && oldControl.isArtefact) removeItem(oldControl.pivoted, this.name);
                        pushUnique(newControl.pivoted, this.name);
                        this[attr] = newControl;
                    }
                }
    
                else if (attr.indexOf('Path') > 0) {
                    
                    if (newControl && newControl.isArtefact) {
    
                        if (oldControl && oldControl.isArtefact) removeItem(oldControl.pathed, this.name);
                        pushUnique(newControl.pathed, this.name);
                        this[attr] = newControl;
                    }
                }
    
                else if (attr.indexOf('Particle') > 0) {
                    
                    newControl = (item.substring) ? particle[item] : item;
  • ¶

    For particles, we only care in cases where the particle has not yet been generated

    • Net entitys only create their particles at the time of their first Display cycle stamp operation, which will often be after the curve entity (line, quadratic, bezier) has been defined and created.
                    if (!newControl) {
    
                        this.updateDirty();
    
                        if (label === 'startControl') this.dirtyStartControl = true;
                        else if (label === 'control') this.dirtyControl = true;
                        else if (label === 'endControl') this.dirtyEndControl = true;
                        else this.dirtyEnd = true;
    
                        this[attr] = item;
                    }
                }
            }
        };
  • ¶

    buildPathPositionObject - internal function called by getPathPositionData

        P.buildPathPositionObject = function (unit, myLen) {
    
            if (unit) {
    
                let [unitSpecies, ...vars] = unit;
    
                let myPoint, angle;
    
                switch (unitSpecies) {
    
                    case 'linear' :
                        myPoint = this.positionPointOnPath(this.getLinearXY(myLen, ...vars));
                        angle = this.getLinearAngle(myLen, ...vars);
                        break;
    
                    case 'quadratic' :
                        myPoint = this.positionPointOnPath(this.getQuadraticXY(myLen, ...vars));
                        angle = this.getQuadraticAngle(myLen, ...vars);
                        break;
                        
                    case 'bezier' :
                        myPoint = this.positionPointOnPath(this.getBezierXY(myLen, ...vars));
                        angle = this.getBezierAngle(myLen, ...vars);
                        break;
                }
    
                let flipAngle = 0
                if (this.flipReverse) flipAngle++;
                if (this.flipUpend) flipAngle++;
    
                if (flipAngle === 1) angle = -angle;
    
                angle += this.roll;
    
                let stamp = this.currentStampPosition,
                    lineOffset = this.controlledLineOffset,
                    localBox = this.localBox;
    
                myPoint.x += lineOffset[0];
                myPoint.y += lineOffset[1];
    
                myPoint.angle = angle;
    
                return myPoint;
            }
            return false;
        };
  • ¶

    Display cycle functionality

  • ¶

    prepareStamp - the purpose of most of these actions is described in the entity mixin function that this function overwrites

        P.prepareStamp = function() {
    
            if (this.dirtyHost) this.dirtyHost = false;
  • ¶

    preparePinsForStamp function defined in line, quadratic and bezier modules

            if (this.dirtyPins.length) {
    
                this.preparePinsForStamp();
            }
  • ¶

    dirtyLock flags (one for each control) - trigger cleanLock functions - which in turn set appropriate dirty flags on the entity.

            if (this.dirtyLock) this.cleanLock();
            if (this.dirtyStartControlLock) this.cleanControlLock('startControl');
            if (this.dirtyEndControlLock) this.cleanControlLock('endControl');
            if (this.dirtyControlLock) this.cleanControlLock('control');
            if (this.dirtyEndLock) this.cleanControlLock('end');
    
            if (this.dirtyScale || this.dirtySpecies || this.dirtyDimensions || this.dirtyStart || this.dirtyStartControl || this.dirtyEndControl || this.dirtyControl || this.dirtyEnd || this.dirtyHandle) {
    
                this.dirtyPathObject = true;
  • ¶

    dirtySpecies flag is specific to Shape entitys

                if (this.useStartAsControlPoint && this.dirtyStart) {
    
                    this.dirtySpecies = true;
                    this.pathCalculatedOnce = false;
                }
  • ¶

    pathCalculatedOnce - calculating path data is an expensive operation - use this flag to limit the calculation to run only when needed

                if (this.dirtyScale || this.dirtySpecies || this.dirtyStartControl || this.dirtyEndControl || this.dirtyControl || this.dirtyEnd)  this.pathCalculatedOnce = false;
            }
    
            if (this.isBeingDragged || this.lockTo.indexOf('mouse') >= 0 || this.lockTo.indexOf('particle') >= 0) {
    
                this.dirtyStampPositions = true;
  • ¶

    useStartAsControlPoint

    • When false, this flag indicates that line, quadratic and bezier shapes should treat start Coordinate updates as an instruction to move the entire Shape.
    • When true, the Coordinate is used to define the shape of the Shape relative to its other control/end Coordinates
                if (this.useStartAsControlPoint) {
    
                    this.dirtySpecies = true;
                    this.dirtyPathObject = true;
                    this.pathCalculatedOnce = false;
                }
            }
    
            if (this.dirtyScale) this.cleanScale();
    
            if (this.dirtyStart) this.cleanStart();
    
            if (this.dirtyStartControl || this.startControlLockTo === 'particle') this.cleanControl('startControl');
            if (this.dirtyEndControl || this.endControlLockTo === 'particle') this.cleanControl('endControl');
            if (this.dirtyControl || this.controlLockTo === 'particle') this.cleanControl('control');
            if (this.dirtyEnd || this.endLockTo === 'particle') this.cleanControl('end');
    
            if (this.dirtyOffset) this.cleanOffset();
            if (this.dirtyRotation) this.cleanRotation();
    
            if (this.dirtyStampPositions) this.cleanStampPositions();
  • ¶

    cleanSpecies - creates the SVG path String which will be used by cleanPathObject - each species creates the local path in its own way

            if (this.dirtySpecies) this.cleanSpecies();
            if (this.dirtyPathObject) this.cleanPathObject();
    
            if (this.dirtyPositionSubscribers) {
    
                this.updatePositionSubscribers();
                this.updateControlPathSubscribers();
            }
        };
  • ¶

    cleanControlLock - internal helper function - called by prepareStamp

        P.cleanControlLock = function (label) {
    
            let capLabel = capitalize(label);
    
            this[`dirty${capLabel}Lock`] = false;
            this[`dirty${capLabel}`] = true;
        };
  • ¶

    cleanControl - internal helper function - called by prepareStamp

        P.cleanControl = function (label) {
    
            let capLabel = capitalize(label);
    
            this[`dirty${capLabel}`] = false;
    
            let pivotLabel = `${label}Pivot`,
                pathLabel = `${label}Path`,
                particleLabel = `${label}Particle`,
                pivot = this[pivotLabel],
                path = this[pathLabel],
                part = this[particleLabel],
                art, pathData;
    
            if (pivot && pivot.substring) {
    
                art = artefact[pivot];
    
                if (art) pivot = art;
            }
    
            if (path && path.substring) {
    
                art = artefact[path];
    
                if (art) path = art;
            }
    
            if (part && part.substring) {
    
                art = particle[part];
    
                if (art) part = art;
            }
    
            let lock = this[`${label}LockTo`], 
                x, y, ox, oy, here, host, dims, flag,
                raw = this[label],
                current = this[`current${capLabel}`];
    
            if (lock === 'pivot' && (!pivot || pivot.substring)) lock = 'coord';
            else if (lock === 'path' && (!path || path.substring)) lock = 'coord';
            else if (lock === 'particle' && (!part || part.substring)) lock = 'coord';
    
            switch(lock) {
    
                case 'pivot' :
    
                    if (this.pivotCorner && pivot.getCornerCoordinate) {
    
                        [x, y] = pivot.getCornerCoordinate(this[`${label}PivotCorner`]);
                    }
                    else [x, y] = pivot.currentStampPosition;
    
                    if (!this.addPivotOffset) {
    
                        [ox, oy] = pivot.currentOffset;
                        x -= ox;
                        y -= oy;
                    }
    
                    break;
    
                case 'path' :
    
                    pathData = this.getControlPathData(path, label, capLabel);
    
                    x = pathData.x;
                    y = pathData.y;
    
                    if (!this.addPathOffset) {
    
                        x -= path.currentOffset[0];
                        y -= path.currentOffset[1];
                    }
    
                    break;
    
                case 'particle' :
    
                    x = part.position.x;
                    y = part.position.y;
                    this.pathCalculatedOnce = false;
    
                    break;
    
                case 'mouse' :
    
                    here = this.getHere();
    
                    x = here.x || 0;
                    y = here.y || 0;
    
                    break;
    
                default :
                    
                    x = y = 0;
    
                    host = this.getHost();
    
                    if (host) {
    
                        dims = host.currentDimensions;
    
                        if (dims) {
    
                            this.cleanPosition(current, raw, dims);
                            [x, y] = current;
                        }
                    }
            }
    
            current[0] = x;
            current[1] = y;
    
            this.dirtySpecies = true;
            this.dirtyPathObject = true;
            this.dirtyPositionSubscribers = true;
        };
  • ¶

    getControlPathData - internal helper function - called by cleanControl

        P.getControlPathData = function (path, label, capLabel) {
    
            let checkAttribute = this[`current${capLabel}PathData`];
    
            if (checkAttribute) return checkAttribute;
    
            let pathPos = this[`${label}PathPosition`],
                tempPos = pathPos,
                pathData = path.getPathPositionData(pathPos);
    
            if (pathPos < 0) pathPos += 1;
            if (pathPos > 1) pathPos = pathPos % 1;
    
            pathPos = parseFloat(pathPos.toFixed(6));
            if (pathPos !== tempPos) this[`${label}PathPosition`] = pathPos;
    
            if (pathData) {
    
                this[`current${capLabel}PathData`] = pathData;
                return pathData;
            }
    
            else {
    
                let host = this.getHost();
    
                if (host) {
    
                    let dims = host.currentDimensions;
    
                    if (dims) {
    
                        let current = this[`current${capLabel}`];
    
                        this.cleanPosition(current, this[label], dims);
    
                        return {
                            x: current[0],
                            y: current[1]
                        };
                    }
                }
                return {
                    x: 0,
                    y: 0
                };
            }
        };
  • ¶

    updateControlPathSubscribers

        P.updateControlPathSubscribers = function () {
  • ¶

    THIS WON’T WORK - got rid of these ‘subscriber’ Arrays!

            let items = [].concat(this.endSubscriber, this.endControlSubscriber, this.controlSubscriber, this.startControlSubscriber);
    
            items.forEach(name => {
    
                let instance = artefact[name];
    
                if (instance) {
    
                    if (instance.type === 'Line' || instance.type === 'Quadratic' || instance.type === 'Bezier') {
    
                        if (instance.type === 'Quadratic') {
    
                            instance.dirtyControl = true;
                            instance.currentControlPathData = false;
                        }
    
                        else if (instance.type === 'Bezier') {
    
                            instance.dirtyStartControl = true;
                            instance.dirtyEndControl = true;
                            instance.currentStartControlPathData = false;
                            instance.currentEndControlPathData = false;
                        }
                        instance.currentEndPathData = false;
                        instance.dirtyEnd = true;
                    }
                    instance.currentPathData = false;
                    instance.dirtyStart = true;
                }
            });
        };
  • ¶

    Return the prototype

        return P;
    };