Home Reference Source

src/layout/TrueSizedLayoutDockHelper.js

/**
 @author: Karl Lundfall (lundfall)
 @license NPOSL-3.0
 @copyright Bizboard, 2015

 Adapted version of the LayoutDockHelper made by Hein Rutjes in famous-flex
 */


import LayoutUtility from 'famous-flex/LayoutUtility';

/**
 * @class
 * @param {LayoutContext} context layout-context
 * @param {Object} [options] additional options
 * @param {Object} [options.margins] margins to start out with (default: 0px)
 * @param {Number} [options.translateZ] z-index to use when translating objects (default: 0)
 * @alias module:LayoutDockHelper
 */
export function TrueSizedLayoutDockHelper(context, options) {
    var size = context.size;
    this._size = size;
    this._context = context;
    this._options = options;
    this._data = {
        z: (options && options.translateZ) ? options.translateZ : 0
    };
    var margins = [0, 0, 0, 0];
    if (options && options.margins) {
        margins = LayoutUtility.normalizeMargins(options.margins);
    }
    this._initialData = {
        left: margins[3],
        top: margins[0],
        right: size[0] - margins[1],
        bottom: size[1] - margins[2]
    };
    this._data.left = this._initialData.left;
    this._data.top = this._initialData.top;
    this._data.right = this._initialData.right;
    this._data.bottom = this._initialData.bottom;
}

/**
 * Parses the layout-rules based on a JSON data object.
 *
 * **Example:**
 *
 * ```JSON
 * [
 *   ['top', 'header', 50],
 *   ['bottom', 'footer', 50, 10], // z-index: 10
 *   ['margins', [10, 5]], // marginate remaining space: 10px top/bottom, 5px left/right
 *   ['fill', 'content']
 * ]
 * ```
 *
 * @param {Object} data JSON object
 */
TrueSizedLayoutDockHelper.prototype.parse = function (data) {
    for (var i = 0; i < data.length; i++) {
        var rule = data[i];
        var value = (rule.length >= 3) ? rule[2] : undefined;
        if (rule[0] === 'top') {
            this.top(rule[1], value, (rule.length >= 4) ? rule[3] : undefined);
        }
        else if (rule[0] === 'left') {
            this.left(rule[1], value, (rule.length >= 4) ? rule[3] : undefined);
        }
        else if (rule[0] === 'right') {
            this.right(rule[1], value, (rule.length >= 4) ? rule[3] : undefined);
        }
        else if (rule[0] === 'bottom') {
            this.bottom(rule[1], value, (rule.length >= 4) ? rule[3] : undefined);
        }
        else if (rule[0] === 'fill') {
            this.fill(rule[1], (rule.length >= 3) ? rule[2] : undefined);
        }
        else if (rule[0] === 'margins') {
            this.margins(rule[1]);
        }
    }
};

/**
 * Dock the node to the top. Sizes can also be specified as ~size, which makes them truesizes
 *
 * @param {LayoutNode|String} [node] layout-node to dock, when omitted the `height` argument argument is used for padding
 * @param {Array}  size of the node. If number, draws only one dimension and leaves the other one undefined
 * @param {Number} [z] z-index to use for the node
 * @param {Number} space the space inserted before this item, defaults to 0
 * @param extraTranslation
 * @return {TrueSizedLayoutDockHelper} this
 */
TrueSizedLayoutDockHelper.prototype.top = function (renderableName, size, space = 0, extraTranslation = [0, 0, 0], innerSize, otherSpecs) {
    let [width, height] = this._setupAccordingToDimension(size, 1);
    if (this._data.top !== this._initialData.top) {
        this._data.top += space;
    }

    this._context.set(renderableName, {
        size: innerSize || ([width || (this._data.right - this._data.left), this._ensureTrueSize(height)]),
        translate: this._addTranslations([this._data.left, this._data.top, this._data.z], extraTranslation),
        ...otherSpecs
    });
    /* If height was negative, then it is true sized and it needs to be tild'd to return to original */
    this._data.top += this._resolveSingleSize(height);
    return this;
};

/**
 * Dock the node to the left. Sizes can also be specified as ~size, which makes them truesizes
 *
 * @param {LayoutNode|String} [node] layout-node to dock, when omitted the `width` argument argument is used for padding
 * @param {Array}  size of the node. If number, draws only one dimension and leaves the other one undefined
 * @param {Number} [z] z-index to use for the node
 * @param {Number} space the space inserted before this item, defaults to 0
 * @param extraTranslation
 * @return {TrueSizedLayoutDockHelper} this
 */
TrueSizedLayoutDockHelper.prototype.left = function (renderableName, size, space = 0, extraTranslation = [0, 0, 0], innerSize, otherSpecs) {
    let [width, height] = this._setupAccordingToDimension(size, 0);
    if (this._data.left !== this._initialData.left) {
        this._data.left += space;
    }
    this._context.set(renderableName, {
        size: innerSize || ([this._ensureTrueSize(width), height || (this._data.bottom - this._data.top)]),
        translate: this._addTranslations([this._data.left, this._data.top, this._data.z], extraTranslation),
        ...otherSpecs
    });
    this._data.left += this._resolveSingleSize(width);
    return this;
};

/**
 * Dock the node to the bottom. Sizes can also be specified as ~size, which makes them truesizes
 *
 * @param {LayoutNode|String} [node] layout-node to dock, when omitted the `height` argument argument is used for padding
 * @param {Array}  size of the node. If number, draws only one dimension and leaves the other one undefined
 * @param {Number} [z] z-index to use for the node
 * @param {Number} space the space inserted before this item, defaults to 0
 * @param extraTranslation
 * @return {TrueSizedLayoutDockHelper} this
 */
TrueSizedLayoutDockHelper.prototype.bottom = function (renderableName, size, space = 0, extraTranslation = [0, 0, 0], innerSize, otherSpecs) {
    let [width, height] = this._setupAccordingToDimension(size, 1);
    if (this._data.bottom !== this._initialData.bottom) {
        this._data.bottom -= space;
    }
    this._data.bottom -= this._resolveSingleSize(height);
    this._context.set(renderableName, {
        size: innerSize || ([width || (this._data.right - this._data.left), this._ensureTrueSize(height)]),
        translate: this._addTranslations([this._data.left, this._data.bottom, this._data.z], extraTranslation),
        ...otherSpecs
    });
    return this;
};

/**
 * Dock the node to the right. Sizes can also be specified as ~size, which makes them truesizes
 *
 * @param {LayoutNode|String} [node] layout-node to dock, when omitted the `width` argument argument is used for padding
 * @param {Array}  size of the node. If number, draws only one dimension and leaves the other one undefined
 * @param {Number} [this._data.z] z-index to use for the node
 * @param {Number} space the space inserted before this item, defaults to 0
 * @param extraTranslation
 * @return {TrueSizedLayoutDockHelper} this
 */
TrueSizedLayoutDockHelper.prototype.right = function (renderableName, size, space = 0, extraTranslation = [0, 0, 0], innerSize, otherSpecs) {
    let [width, height] = this._setupAccordingToDimension(size, 0);
    if (this._data.right !== this._initialData.right) {
        this._data.right -= space;
    }
    this._data.right -= this._resolveSingleSize(width);
    this._context.set(renderableName, {
        size: innerSize || ([this._ensureTrueSize(width), height || (this._data.bottom - this._data.top)]),
        translate: this._addTranslations([this._data.right, this._data.top, this._data.z], extraTranslation),
        ...otherSpecs
    });
    return this;
};

/**
 * Fills the node to the remaining content.
 *
 * @param {LayoutNode|String} node layout-node to dock
 * @param {Number} [z] z-index to use for the node
 * @return {TrueSizedLayoutDockHelper} this
 */
TrueSizedLayoutDockHelper.prototype.fill = function (renderableName, size, translate = [0, 0, 0], otherSpecs) {
    this._context.set(renderableName, {
        size: [size[0] || this._data.right - this._data.left, size[1] || this._data.bottom - this._data.top],
        translate: this._addTranslations([this._data.left, this._data.top, this._data.z], translate),
        ...otherSpecs
    });
    return this;
};

/**
 * Applies indent margins to the remaining content.
 *
 * @param {Number|Array} margins margins shorthand (e.g. '5', [10, 10], [5, 10, 5, 10])
 * @return {TrueSizedLayoutDockHelper} this
 */
TrueSizedLayoutDockHelper.prototype.margins = function (margins) {
    margins = LayoutUtility.normalizeMargins(margins);
    this._data.left += margins[3];
    this._data.top += margins[0];
    this._data.right -= margins[1];
    this._data.bottom -= margins[2];
    this._initialData.left = this._data.left;
    this._initialData.right = this._data.right;
    this._initialData.top = this._data.top;
    this._initialData.bottom = this._data.bottom;
    return this;
};

TrueSizedLayoutDockHelper.prototype._resolveSingleSize = function (size) {
    return size < 0 ? ~size : size;
};
TrueSizedLayoutDockHelper.prototype._addTranslations = function (translation1, translation2) {
    return [translation1[0] + translation2[0], translation1[1] + translation2[1], translation1[2] + translation2[2]];
};
TrueSizedLayoutDockHelper.prototype._ensureTrueSize = function (size) {
    return size < 0 ? true : size;
};


TrueSizedLayoutDockHelper.prototype._setupAccordingToDimension = function (size, dim) {
    let height;
    let width;
    if (size instanceof Array) {
        let orthogonalDimension = dim ? 0 : 1;
        let adjustedSize = [size[0], size[1]];
        if (size[orthogonalDimension] < 0) {
            /* If a true size was specified as an orhtogonal dimension, we just set it to true, as we don't need to save the value anywhere here */
            adjustedSize[orthogonalDimension] = true;
        }
        width = adjustedSize[0];
        height = adjustedSize[1];
    } else {
        width = size;
    }
    return [width, height];
};

/**
 * Gets the current left/right/top/bottom/z bounds used by the dock-helper.
 *
 * @return {Object} `{left: x, right: x, top: x, bottom: x, z: x}`
 */
TrueSizedLayoutDockHelper.prototype.get = function () {
    return this._data;
};