Source: utils/ScaleContainer.js

/**
 * Scale 9 Container.
 * e.g. useful for scalable buttons.
 *
 * @class ScaleContainer
 * @extends PIXI.Container
 * @memberof GOWN
 * @constructor
 * @param texture The PIXI texture {PIXI.Texture}
 * @param rect The rectangle with position and dimensions of the center piece.
 * Will be used to calculate positions of all other pieces {PIXI.Rectangle}
 * @param [middleWidth] The alternative width to crop the center piece
 * (only needed if we want to scale the image smaller than the original) {Number}
 * @param [centerHeight] The alternative height to crop the center piece
 * (only needed if we want to scale the image smaller than the original) {Number}
 */
function ScaleContainer(texture, rect, middleWidth, centerHeight) {
    PIXI.Container.call( this );

    /**
     * The rectangle with position and dimensions of the center piece.
     * Will be used to calculate positions of all other pieces.
     *
     * @type PIXI.Rectangle
     */
    this.rect = rect;

    /**
     * The base texture of the scale container
     *
     * @type PIXI.BaseTexture
     */
    this.baseTexture = texture.baseTexture;

    /**
     * The frame of the scale container
     *
     * @type PIXI.Rectangle
     */
    this.frame = texture.frame;

    /**
     * The width of the scale container
     *
     * @private
     * @type Number
     */
    this._width = this.frame.width;

    /**
     * The height of the scale container
     *
     * @private
     * @type Number
     */
    this._height = this.frame.height;

    // left / middle / right width
    var lw = rect.x;
    var mw = rect.width;
    var rw = this.frame.width - (mw + lw);

    // top / center / bottom height
    var th = rect.y;
    var ch = rect.height;
    var bh = this.frame.height - (ch + th);

    middleWidth = middleWidth || mw;
    centerHeight = centerHeight || ch;

    /**
     * Calculated min. width based on tile sizes in pixel without scaling
     * (if middleWidth is not set it is the same as the width of the
     * texture in the atlas)
     *
     * @type Number
     */
    this.minWidth = lw + middleWidth + rw;

    /**
     * Calculated min. height based on tile sizes in pixel without scaling
     * (if middleWidth is not set it is the same as the height of the
     * texture in the atlas)
     *
     * @type Number
     */
    this.minHeight = th + centerHeight + bh;

    if (lw > 0 && th > 0) {
        /**
         * The top left sprite
         *
         * @type {PIXI.Sprite}
         */
        this.tl = this._getTexture(0, 0, lw, th);
        this.addChild(this.tl);
    }

    if (mw > 0 && th > 0) {
        /**
         * The top middle sprite
         *
         * @type {PIXI.Sprite}
         */
        this.tm = this._getTexture(lw, 0, middleWidth, th);
        this.addChild(this.tm);
        this.tm.x = lw;
    }

    if (rw > 0 && th > 0) {
        /**
         * The top right sprite
         *
         * @type {PIXI.Sprite}
         */
        this.tr = this._getTexture(lw + mw, 0, rw, th);
        this.addChild(this.tr);
    }

    if (lw > 0 && ch > 0) {
        /**
         * The center left sprite
         *
         * @type {PIXI.Sprite}
         */
        this.cl = this._getTexture(0, th, lw, centerHeight);
        this.addChild(this.cl);
        this.cl.y = th;
    }

    if (mw > 0 && ch > 0) {
        /**
         * The center middle sprite
         *
         * @type {PIXI.Sprite}
         */
        this.cm = this._getTexture(lw, th, middleWidth, centerHeight);
        this.addChild(this.cm);
        this.cm.y = th;
        this.cm.x = lw;
    }

    if (rw > 0 && ch > 0) {
        /**
         * The center right sprite
         *
         * @type {PIXI.Sprite}
         */
        this.cr = this._getTexture(lw + mw, th, rw, centerHeight);
        this.addChild(this.cr);
        this.cr.y = th;
    }

    if (lw > 0 && bh > 0) {
        /**
         * The bottom left sprite
         *
         * @type {PIXI.Sprite}
         */
        this.bl = this._getTexture(0, th + ch, lw, bh);
        this.addChild(this.bl);
    }

    if (mw > 0 && bh > 0) {
        /**
         * The bottom middle sprite
         *
         * @type {PIXI.Sprite}
         */
        this.bm = this._getTexture(lw, th + ch, middleWidth, bh);
        this.addChild(this.bm);
        this.bm.x = lw;
    }

    if (rw > 0 && bh > 0) {
        /**
         * The bottom right sprite
         *
         * @type {PIXI.Sprite}
         */
        this.br = this._getTexture(lw + mw, th + ch, rw, bh);
        this.addChild(this.br);
    }
}

ScaleContainer.prototype = Object.create( PIXI.Container.prototype );
ScaleContainer.prototype.constructor = ScaleContainer;
module.exports = ScaleContainer;

/**
 * Set the scaling width and height
 *
 * @private
 */
ScaleContainer.prototype._updateScales = function() {
    this._positionTilable();

    var scaleOriginals = this.scaleOriginals = {};

    var scaleOriginal = function(name, elem) {
        if (elem && elem.width && elem.height) {
            scaleOriginals[name] = {
                width: elem.width,
                height: elem.height
            };
        }
    };

    scaleOriginal('tl', this.tl);
    scaleOriginal('tm', this.tm);
    scaleOriginal('tr', this.tr);

    scaleOriginal('cl', this.cl);
    scaleOriginal('cm', this.cm);
    scaleOriginal('cr', this.cr);

    scaleOriginal('bl', this.bl);
    scaleOriginal('bm', this.bm);
    scaleOriginal('br', this.br);
};

/**
 * Create a new texture from a base-texture by a given dimensions
 *
 * @param x The x-position {Number}
 * @param y The y-position {Number}
 * @param w The width {Number}
 * @param h The height {Number}
 * @return {PIXI.Sprite} The sprite with the created texture
 * @private
 */
ScaleContainer.prototype._getTexture = function(x, y, w, h) {
    var frame = new PIXI.Rectangle(this.frame.x+x, this.frame.y+y, w, h);
    var t = new PIXI.Texture(this.baseTexture, frame, frame.clone(), null);
    return new PIXI.Sprite(t);
};

/**
 * The width of the container. Setting this will redraw the component.
 *
 * @name GOWN.ScaleContainer#width
 * @type Number
 */
Object.defineProperty(ScaleContainer.prototype, 'width', {
    get: function() {
        return this._width;
    },
    set: function(value) {
        if (this._width !== value) {
            if (this.minWidth && this.minWidth > 0 &&
                value < this.minWidth) {
                value = this.minWidth;
            }
            this._width = value;
            this.invalid = true;
            this._updateScales();
        }
    }
});

/**
 * The height of the container. Setting this will redraw the component.
 *
 * @name GOWN.ScaleContainer#height
 * @type Number
 */
Object.defineProperty(ScaleContainer.prototype, 'height', {
    get: function() {
        return this._height;
    },
    set: function(value) {
        if (this._height !== value) {
            if (this.minHeight && this.minHeight > 0 &&
                value < this.minHeight) {
                value = this.minHeight;
            }
            this._height = value;
            this.invalid = true;
            this._updateScales();
        }
    }
});

/**
 * Update before draw call (reposition textures)
 *
 * @private
 */
ScaleContainer.prototype.redraw = function() {
    if (this.invalid) {
        this._positionTilable();
        this.invalid = false;
    }
};

/**
 * Recalculate the position of the tiles (every time the width/height changes)
 *
 * @private
 */
ScaleContainer.prototype._positionTilable = function() {
    // left / middle / right width
    var lw = this.rect.x;
    var mw = this.rect.width;
    var rw = this.frame.width - (mw + lw);

    // top / center / bottom height
    var th = this.rect.y;
    var ch = this.rect.height;
    var bh = this.frame.height - (ch + th);

    var rightX = this._width - rw;
    var bottomY = this._height - bh;
    if (this.cr) {
        this.cr.x = rightX;
    }
    if (this.br) {
        this.br.x = rightX;
        this.br.y = bottomY;
    }
    if (this.tr) {
        this.tr.x = rightX;
    }

    var middleWidth = this._width - (lw + rw);
    var centerHeight = this._height - (th + bh);
    if (this.cm) {
        this.cm.width = middleWidth;
        this.cm.height = centerHeight;
    }
    if (this.bm) {
        this.bm.width = middleWidth;
        this.bm.y = bottomY;
    }
    if (this.tm) {
        this.tm.width = middleWidth;
    }
    if (this.cl) {
        this.cl.height = centerHeight;
    }
    if (this.cr) {
        this.cr.height = centerHeight;
    }

    if (this.bl) {
        this.bl.y = bottomY;
    }
};

/**
 * Helper function that creates a sprite that will contain a texture from
 * the TextureCache based on the frameId.
 * The frame ids are created when a Texture packer file has been loaded
 *
 * @param frameId The frame id of the texture in the cache {String}
 * @param rect Defines the tilable area {Rectangle}
 * @return {GOWN.ScaleContainer} A new scalable container (e.g. a button)
 * using a texture from the texture cache matching the frameId
 */
ScaleContainer.fromFrame = function(frameId, rect) {
    var texture = PIXI.utils.TextureCache[frameId];
    if(!texture) {
        throw new Error('The frameId "' + frameId + '" does not exist ' +
                        'in the texture cache');
    }
    return new ScaleContainer(texture, rect);
};