Parse

File Parse setunset.js

This tree is parsed live from the source file.

Classes

  • {{ item.name }}

    • {{ key }}

Not Classes

{{ getTree() }}

Comments

{{ getTreeComments() }}

Source

            /*
    The "Set Unset" tool acts similar to the _ctx.save()_ method, by applying
    changes to the context, and then _unsetting_ after usage.

    This allows the _switching_ of a property of the context with `set` and `unset`
    values previously assigned to changed properties are reapplied - essentially _wrapping_
    some drawing with context changes.

        su = new SetUnset({
            lineWidth: 10
        })

        // lineWidth == ...
        su.set(ctx)
        // lineWidth == 10
        su.unset(ctx)
        // lineWidth == ...

    Implement shortcuts:

        class Stroke extends SetUnset {
            getOpts() {
                let supported = new Set([
                    , "lineWidth"
                ])

                let map = {
                    color: 'strokeStyle'
                }
                let functional = {
                    dash: 'lineDashKeyApply'
                }
                return [supported, map, functional]
            }
        }

        st = new Stroke({
            color: 'red'
        })
 */
class SetUnset {

    /* Perform _stroke_ with styling and stoking; but with a convenient
    on/off without using the context switcher. */
    constructor(settings={}) {
        this.settings = settings;
        this._cache = {}
        this.steps = []
        this._enabled = settings.enabled === undefined? true: settings.enabled

        this.onCreate(this.update(settings))
    }

    onCreate(cachedData) {

    }

    update(settings) {
        /* Apply changes for the persistent info*/
        let [o, steps] = this.prepare(settings)
        this.steps = this.steps.concat(steps)
        return Object.assign(this._cache, o)
    }

    getOpts() {

        let props = Object.getOwnPropertyNames(CanvasRenderingContext2D.prototype)
        let rawProps = new Set()
        for(let k of props) {
            let v = props[k]
            if(typeof(v) == 'function') {
                continue
            }
            let desc = Object.getOwnPropertyDescriptor(CanvasRenderingContext2D.prototype, k)
            if(desc.set != undefined) {
                rawProps.add(k)
            }
        }

        let sugarProps = {}
        let functionalProps = {}
        return [
            rawProps, {}, {}
        ]
    }

    prepare(settings){
        /*
            when _settings_ to the ctx
            + first check if its a raw prop,
            + else then resolve from the map.
            + keep the last value
            + Apply through the given function
            + accept a optional return function, to call during unset.

            Therefore the stroker has a _state_
            This should occur _early_ such that the _set_ here runs a chain of
            on/off functions in a map.

            Which means:

            1. for each key resolve ctx (real prop map key), on method, off method
            2. store those until _set_
            3. iter on map; calling each func with the ctx and the value
            4. iter off map, calling each with the stored value, reapplying in the func.

         */

        let [
            /* ctx properties of which don't need map adapting, */
            rawProps
            /* Convenience names to real names */
            , sugarProps
            /* Special methods to perform _more than_ a prop key.*/
            , functionalProps
        ] = Object.values(this.getOpts())

        let isRaw = (k) => rawProps.has(k)

        // , dash: 'setLineDash'
        let generalAcceptor = this.genericKeyApply.bind(this)
        let functionalAcceptor = this.functionKeyApply.bind(this)
        let adds = {};
        let steps = [];

        for(let key in settings) {
            let value = settings[key]
            if(isRaw(key)) {
                // Generic set unset.
                adds[key] = {f:generalAcceptor, k: key, v: value}
                continue
            }

            if(key in sugarProps) {
                // sugar; get real and apply generic.
                adds[sugarProps[key]] = {f:generalAcceptor, k: key, v: value}
                continue
            }

            // A custom prop such as lineDash
            if(key in functionalProps) {
                // sugar; get real and apply generic.
                // debugger

                let res = functionalProps[key]
                /* If _prepare_ function, this should return the actual acceptor */
                if(typeof(res) == 'string') {
                    adds[res] = {f:functionalAcceptor, k: key, v: value}
                    continue
                }

                this[res[0]]();
                let ref = {f:functionalAcceptor, k: key, v: value}
                adds[res[1]] = ref

                if(res[2]) {
                    // step func.

                    ref['step'] = res[2]
                    steps.push(res[1])
                }
            }
        }

        return [adds, steps]
    }

    genericKeyApply(ctx, key, newValue) {
        /* Receive a ctx key property (e.g. lineWidth) and apply the newValue

        Return the previously applied value and a function to remove reverse change.
         */
        let existing = ctx[key]
        ctx[key] = newValue
        return { v: existing, f: this.genericKeyRemove.bind(this)}
    }

    genericKeyRemove(ctx, key, newValue){
        /*
        the newValue given is the original value applied to this key, before
        the change occured (the cached value). Therefore the _remove_ is
        identical to the _apply_.
        */
        return this.genericKeyApply(ctx, key,newValue)
    }

    functionKeyApply(ctx, assignment, newValue, key) {
        /* Call upon the `aasignment` function, with the key and value.
        This expects a {f: function,v: value} response */
        try {

            return this[assignment](ctx, key, newValue)
        } catch(e) {
            if(e.name == 'TypeError') {
                let m = `function method does not exist: ${assignment}`
                // e.message = m
            }

            throw e
        }
    }

    set(ctx, settings=this.settings){

        if(!this._enabled) {
            return
        }

        let cacheProps = this.getCacheBeforeApply();

        let keep = {}
        for(let key in cacheProps) {
            /* Iter all the assignments,
            functionally calling each */
            let entry = cacheProps[key]
            let stored = entry.f(ctx, key, entry.v, entry.k, cacheProps)
            keep[key] = stored
        }

        this._applied = keep
    }

    getCacheBeforeApply() {
        return this._cache
    }

    unset(ctx, settings) {
        /*Remove any currently applied styles, by reading the
        cache and performing the reverse. */

        if((!this._enabled) && settings == undefined) {
            return
        }


        let cacheProps = this._applied;

        let keep = {}
        for(let key in cacheProps) {
            /* Iter all the assignments,
            functionally calling each */
            let entry = cacheProps[key]
            let stored = entry.f(ctx, key, entry.v, entry.k, cacheProps)
            // keep[key] = stored
        }
        // ctx.setLineDash([])
    }

    draw() {
        /* a start but probably more dynamic. */
    }

    wrap(ctx, settings, func) {
        /* Wrap the call with a start stop

            wrap(ctx, ()=>{})
            wrap(ctx, settings, ()=>{})
        */
        if(func == undefined) {
            func = settings;
            settings = this.settings;
        }

        this.set(ctx, settings)
        func(ctx)
        this.unset(ctx, settings)
    }

    step() {
        /* A _step_ is required when integrating moving values, such as the
        dash offset animation. */

        let ref = this._cache
        if(!ref){ return }


        for(let name of this.steps) {
            const item = ref[name]
            let funcName = item?.step
            try {
                let f = this[funcName](item)
            } catch(e) {
                if(e.name == 'TypeError') {
                    e.message = `step function "${funcName}" does not exist.`
                }
                throw e
            }

            // this[name].bind(this)()
        }
    }

    off(setDisable=true) {
        // undo any _on_ and _pause_ the _on_ function
        this._enabled = !setDisable
    }

    on(setEnable=true) {
        this._enabled = setEnable
    }

}

copy