"use strict";
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * A sizer object for use with the box engine layout functions.
 *
 * #### Notes
 * A box sizer holds the geometry information for an object along an
 * arbitrary layout orientation.
 *
 * For best performance, this class should be treated as a raw data
 * struct. It should not typically be subclassed.
 */
var BoxSizer = /** @class */ (function () {
    function BoxSizer() {
        /**
         * The preferred size for the sizer.
         *
         * #### Notes
         * The sizer will be given this initial size subject to its size
         * bounds. The sizer will not deviate from this size unless such
         * deviation is required to fit into the available layout space.
         *
         * There is no limit to this value, but it will be clamped to the
         * bounds defined by [[minSize]] and [[maxSize]].
         *
         * The default value is `0`.
         */
        this.sizeHint = 0;
        /**
         * The minimum size of the sizer.
         *
         * #### Notes
         * The sizer will never be sized less than this value, even if
         * it means the sizer will overflow the available layout space.
         *
         * It is assumed that this value lies in the range `[0, Infinity)`
         * and that it is `<=` to [[maxSize]]. Failure to adhere to this
         * constraint will yield undefined results.
         *
         * The default value is `0`.
         */
        this.minSize = 0;
        /**
         * The maximum size of the sizer.
         *
         * #### Notes
         * The sizer will never be sized greater than this value, even if
         * it means the sizer will underflow the available layout space.
         *
         * It is assumed that this value lies in the range `[0, Infinity]`
         * and that it is `>=` to [[minSize]]. Failure to adhere to this
         * constraint will yield undefined results.
         *
         * The default value is `Infinity`.
         */
        this.maxSize = Infinity;
        /**
         * The stretch factor for the sizer.
         *
         * #### Notes
         * This controls how much the sizer stretches relative to its sibling
         * sizers when layout space is distributed. A stretch factor of zero
         * is special and will cause the sizer to only be resized after all
         * other sizers with a stretch factor greater than zero have been
         * resized to their limits.
         *
         * It is assumed that this value is an integer that lies in the range
         * `[0, Infinity)`. Failure to adhere to this constraint will yield
         * undefined results.
         *
         * The default value is `1`.
         */
        this.stretch = 1;
        /**
         * The computed size of the sizer.
         *
         * #### Notes
         * This value is the output of a call to [[boxCalc]]. It represents
         * the computed size for the object along the layout orientation,
         * and will always lie in the range `[minSize, maxSize]`.
         *
         * This value is output only.
         *
         * Changing this value will have no effect.
         */
        this.size = 0;
        /**
         * An internal storage property for the layout algorithm.
         *
         * #### Notes
         * This value is used as temporary storage by the layout algorithm.
         *
         * Changing this value will have no effect.
         */
        this.done = false;
    }
    return BoxSizer;
}());
exports.BoxSizer = BoxSizer;
/**
 * The namespace for the box engine layout functions.
 */
var BoxEngine;
(function (BoxEngine) {
    /**
     * Calculate the optimal layout sizes for a sequence of box sizers.
     *
     * This distributes the available layout space among the box sizers
     * according to the following algorithm:
     *
     * 1. Initialize the sizers's size to its size hint and compute the
     *    sums for each of size hint, min size, and max size.
     *
     * 2. If the total size hint equals the available space, return.
     *
     * 3. If the available space is less than the total min size, set all
     *    sizers to their min size and return.
     *
     * 4. If the available space is greater than the total max size, set
     *    all sizers to their max size and return.
     *
     * 5. If the layout space is less than the total size hint, distribute
     *    the negative delta as follows:
     *
     *    a. Shrink each sizer with a stretch factor greater than zero by
     *       an amount proportional to the negative space and the sum of
     *       stretch factors. If the sizer reaches its min size, remove
     *       it and its stretch factor from the computation.
     *
     *    b. If after adjusting all stretch sizers there remains negative
     *       space, distribute the space equally among the sizers with a
     *       stretch factor of zero. If a sizer reaches its min size,
     *       remove it from the computation.
     *
     * 6. If the layout space is greater than the total size hint,
     *    distribute the positive delta as follows:
     *
     *    a. Expand each sizer with a stretch factor greater than zero by
     *       an amount proportional to the postive space and the sum of
     *       stretch factors. If the sizer reaches its max size, remove
     *       it and its stretch factor from the computation.
     *
     *    b. If after adjusting all stretch sizers there remains positive
     *       space, distribute the space equally among the sizers with a
     *       stretch factor of zero. If a sizer reaches its max size,
     *       remove it from the computation.
     *
     * 7. return
     *
     * @param sizers - The sizers for a particular layout line.
     *
     * @param space - The available layout space for the sizers.
     *
     * @returns The delta between the provided available space and the
     *   actual consumed space. This value will be zero if the sizers
     *   can be adjusted to fit, negative if the available space is too
     *   small, and positive if the available space is too large.
     *
     * #### Notes
     * The [[size]] of each sizer is updated with the computed size.
     *
     * This function can be called at any time to recompute the layout for
     * an existing sequence of sizers. The previously computed results will
     * have no effect on the new output. It is therefore not necessary to
     * create new sizer objects on each resize event.
     */
    function calc(sizers, space) {
        // Bail early if there is nothing to do.
        var count = sizers.length;
        if (count === 0) {
            return space;
        }
        // Setup the size and stretch counters.
        var totalMin = 0;
        var totalMax = 0;
        var totalSize = 0;
        var totalStretch = 0;
        var stretchCount = 0;
        // Setup the sizers and compute the totals.
        for (var i = 0; i < count; ++i) {
            var sizer = sizers[i];
            var min = sizer.minSize;
            var max = sizer.maxSize;
            var hint = sizer.sizeHint;
            sizer.done = false;
            sizer.size = Math.max(min, Math.min(hint, max));
            totalSize += sizer.size;
            totalMin += min;
            totalMax += max;
            if (sizer.stretch > 0) {
                totalStretch += sizer.stretch;
                stretchCount++;
            }
        }
        // If the space is equal to the total size, return early.
        if (space === totalSize) {
            return 0;
        }
        // If the space is less than the total min, minimize each sizer.
        if (space <= totalMin) {
            for (var i = 0; i < count; ++i) {
                var sizer = sizers[i];
                sizer.size = sizer.minSize;
            }
            return space - totalMin;
        }
        // If the space is greater than the total max, maximize each sizer.
        if (space >= totalMax) {
            for (var i = 0; i < count; ++i) {
                var sizer = sizers[i];
                sizer.size = sizer.maxSize;
            }
            return space - totalMax;
        }
        // The loops below perform sub-pixel precision sizing. A near zero
        // value is used for compares instead of zero to ensure that the
        // loop terminates when the subdivided space is reasonably small.
        var nearZero = 0.01;
        // A counter which is decremented each time a sizer is resized to
        // its limit. This ensures the loops terminate even if there is
        // space remaining to distribute.
        var notDoneCount = count;
        // Distribute negative delta space.
        if (space < totalSize) {
            // Shrink each stretchable sizer by an amount proportional to its
            // stretch factor. If a sizer reaches its min size it's marked as
            // done. The loop progresses in phases where each sizer is given
            // a chance to consume its fair share for the pass, regardless of
            // whether a sizer before it reached its limit. This continues
            // until the stretchable sizers or the free space is exhausted.
            var freeSpace = totalSize - space;
            while (stretchCount > 0 && freeSpace > nearZero) {
                var distSpace = freeSpace;
                var distStretch = totalStretch;
                for (var i = 0; i < count; ++i) {
                    var sizer = sizers[i];
                    if (sizer.done || sizer.stretch === 0) {
                        continue;
                    }
                    var amt = sizer.stretch * distSpace / distStretch;
                    if (sizer.size - amt <= sizer.minSize) {
                        freeSpace -= sizer.size - sizer.minSize;
                        totalStretch -= sizer.stretch;
                        sizer.size = sizer.minSize;
                        sizer.done = true;
                        notDoneCount--;
                        stretchCount--;
                    }
                    else {
                        freeSpace -= amt;
                        sizer.size -= amt;
                    }
                }
            }
            // Distribute any remaining space evenly among the non-stretchable
            // sizers. This progresses in phases in the same manner as above.
            while (notDoneCount > 0 && freeSpace > nearZero) {
                var amt = freeSpace / notDoneCount;
                for (var i = 0; i < count; ++i) {
                    var sizer = sizers[i];
                    if (sizer.done) {
                        continue;
                    }
                    if (sizer.size - amt <= sizer.minSize) {
                        freeSpace -= sizer.size - sizer.minSize;
                        sizer.size = sizer.minSize;
                        sizer.done = true;
                        notDoneCount--;
                    }
                    else {
                        freeSpace -= amt;
                        sizer.size -= amt;
                    }
                }
            }
        }
        // Distribute positive delta space.
        else {
            // Expand each stretchable sizer by an amount proportional to its
            // stretch factor. If a sizer reaches its max size it's marked as
            // done. The loop progresses in phases where each sizer is given
            // a chance to consume its fair share for the pass, regardless of
            // whether a sizer before it reached its limit. This continues
            // until the stretchable sizers or the free space is exhausted.
            var freeSpace = space - totalSize;
            while (stretchCount > 0 && freeSpace > nearZero) {
                var distSpace = freeSpace;
                var distStretch = totalStretch;
                for (var i = 0; i < count; ++i) {
                    var sizer = sizers[i];
                    if (sizer.done || sizer.stretch === 0) {
                        continue;
                    }
                    var amt = sizer.stretch * distSpace / distStretch;
                    if (sizer.size + amt >= sizer.maxSize) {
                        freeSpace -= sizer.maxSize - sizer.size;
                        totalStretch -= sizer.stretch;
                        sizer.size = sizer.maxSize;
                        sizer.done = true;
                        notDoneCount--;
                        stretchCount--;
                    }
                    else {
                        freeSpace -= amt;
                        sizer.size += amt;
                    }
                }
            }
            // Distribute any remaining space evenly among the non-stretchable
            // sizers. This progresses in phases in the same manner as above.
            while (notDoneCount > 0 && freeSpace > nearZero) {
                var amt = freeSpace / notDoneCount;
                for (var i = 0; i < count; ++i) {
                    var sizer = sizers[i];
                    if (sizer.done) {
                        continue;
                    }
                    if (sizer.size + amt >= sizer.maxSize) {
                        freeSpace -= sizer.maxSize - sizer.size;
                        sizer.size = sizer.maxSize;
                        sizer.done = true;
                        notDoneCount--;
                    }
                    else {
                        freeSpace -= amt;
                        sizer.size += amt;
                    }
                }
            }
        }
        // Indicate that the consumed space equals the available space.
        return 0;
    }
    BoxEngine.calc = calc;
    /**
     * Adjust a sizer by a delta and update its neighbors accordingly.
     *
     * @param sizers - The sizers which should be adjusted.
     *
     * @param index - The index of the sizer to grow.
     *
     * @param delta - The amount to adjust the sizer, positive or negative.
     *
     * #### Notes
     * This will adjust the indicated sizer by the specified amount, along
     * with the sizes of the appropriate neighbors, subject to the limits
     * specified by each of the sizers.
     *
     * This is useful when implementing box layouts where the boundaries
     * between the sizers are interactively adjustable by the user.
     */
    function adjust(sizers, index, delta) {
        // Bail early when there is nothing to do.
        if (sizers.length === 0 || delta === 0) {
            return;
        }
        // Dispatch to the proper implementation.
        if (delta > 0) {
            growSizer(sizers, index, delta);
        }
        else {
            shrinkSizer(sizers, index, -delta);
        }
    }
    BoxEngine.adjust = adjust;
    /**
     * Grow a sizer by a positive delta and adjust neighbors.
     */
    function growSizer(sizers, index, delta) {
        // Compute how much the items to the left can expand.
        var growLimit = 0;
        for (var i = 0; i <= index; ++i) {
            var sizer = sizers[i];
            growLimit += sizer.maxSize - sizer.size;
        }
        // Compute how much the items to the right can shrink.
        var shrinkLimit = 0;
        for (var i = index + 1, n = sizers.length; i < n; ++i) {
            var sizer = sizers[i];
            shrinkLimit += sizer.size - sizer.minSize;
        }
        // Clamp the delta adjustment to the limits.
        delta = Math.min(delta, growLimit, shrinkLimit);
        // Grow the sizers to the left by the delta.
        var grow = delta;
        for (var i = index; i >= 0 && grow > 0; --i) {
            var sizer = sizers[i];
            var limit = sizer.maxSize - sizer.size;
            if (limit >= grow) {
                sizer.sizeHint = sizer.size + grow;
                grow = 0;
            }
            else {
                sizer.sizeHint = sizer.size + limit;
                grow -= limit;
            }
        }
        // Shrink the sizers to the right by the delta.
        var shrink = delta;
        for (var i = index + 1, n = sizers.length; i < n && shrink > 0; ++i) {
            var sizer = sizers[i];
            var limit = sizer.size - sizer.minSize;
            if (limit >= shrink) {
                sizer.sizeHint = sizer.size - shrink;
                shrink = 0;
            }
            else {
                sizer.sizeHint = sizer.size - limit;
                shrink -= limit;
            }
        }
    }
    /**
     * Shrink a sizer by a positive delta and adjust neighbors.
     */
    function shrinkSizer(sizers, index, delta) {
        // Compute how much the items to the right can expand.
        var growLimit = 0;
        for (var i = index + 1, n = sizers.length; i < n; ++i) {
            var sizer = sizers[i];
            growLimit += sizer.maxSize - sizer.size;
        }
        // Compute how much the items to the left can shrink.
        var shrinkLimit = 0;
        for (var i = 0; i <= index; ++i) {
            var sizer = sizers[i];
            shrinkLimit += sizer.size - sizer.minSize;
        }
        // Clamp the delta adjustment to the limits.
        delta = Math.min(delta, growLimit, shrinkLimit);
        // Grow the sizers to the right by the delta.
        var grow = delta;
        for (var i = index + 1, n = sizers.length; i < n && grow > 0; ++i) {
            var sizer = sizers[i];
            var limit = sizer.maxSize - sizer.size;
            if (limit >= grow) {
                sizer.sizeHint = sizer.size + grow;
                grow = 0;
            }
            else {
                sizer.sizeHint = sizer.size + limit;
                grow -= limit;
            }
        }
        // Shrink the sizers to the left by the delta.
        var shrink = delta;
        for (var i = index; i >= 0 && shrink > 0; --i) {
            var sizer = sizers[i];
            var limit = sizer.size - sizer.minSize;
            if (limit >= shrink) {
                sizer.sizeHint = sizer.size - shrink;
                shrink = 0;
            }
            else {
                sizer.sizeHint = sizer.size - limit;
                shrink -= limit;
            }
        }
    }
})(BoxEngine = exports.BoxEngine || (exports.BoxEngine = {}));
