Source: /Users/shukantpal/Web Projects/pixi-ui/bundles/puxi.js/lib/puxi.mjs

/Users/shukantpal/Web Projects/pixi-ui/bundles/puxi.js/lib/puxi.mjs

/*!
 * puxi.js - v0.0.0
 * Compiled Sat, 21 Mar 2020 02:10:49 UTC
 *
 * puxi.js is licensed under the MIT License.
 * http://www.opensource.org/licenses/mit-license
 */
// mjs
import { Point, utils, Container, BLEND_MODES, Graphics, Rectangle, Text, TextStyle, Ticker as Ticker$1, extras, Texture, Sprite as Sprite$1, BaseTexture } from 'pixi.js';
import { DropShadowFilter } from '@pixi/filter-drop-shadow';

/*!
 * @puxi/core - v1.0.0
 * Compiled Sat, 21 Mar 2020 02:10:49 UTC
 *
 * @puxi/core is licensed under the MIT License.
 * http://www.opensource.org/licenses/mit-license
 */

const _items = [];
const DragDropController = {
    add(item, event)
    {
        item._dragDropEventId = event.data.identifier;
        if (_items.indexOf(item) === -1)
        {
            _items.push(item);

            return true;
        }

        return false;
    },
    getItem(object)
    {
        let item = null; let
            index;

        for (let i = 0; i < _items.length; i++)
        {
            if (_items[i] === object)
            {
                item = _items[i];
                index = i;
                break;
            }
        }

        if (item !== null)
        {
            _items.splice(index, 1);

            return item;
        }

        return false;
    },
    getEventItem(event, group)
    {
        let item = null; let index; const
            id = event.data.identifier;

        for (let i = 0; i < _items.length; i++)
        {
            if (_items[i]._dragDropEventId === id)
            {
                if (group !== _items[i].dragGroup)
                {
                    return false;
                }
                item = _items[i];
                index = i;
                break;
            }
        }

        if (item !== null)
        {
            _items.splice(index, 1);

            return item;
        }

        return false;
    },
};

/**
 * @memberof PUXI
 * @class
 */
class Insets {
    constructor() {
        this.reset();
        this.dirtyId = 0;
    }
    reset() {
        this.left = -1;
        this.top = -1;
        this.right = -1;
        this.bottom = -1;
    }
}

/**
 * These are the modes in which an entity can measures its dimension. They are
 * relevant when a layout needs to know the optimal sizes of its children.
 *
 * @memberof PUXI
 * @enum
 * @property {number} UNBOUNDED - no upper limit on bounds. This should calculate the optimal dimensions for the entity.
 * @property {number} EXACTLY - the entity should set its dimension to the one passed to it.
 * @property {number} AT_MOST - the entity should find an optimal dimension below the one passed to it.
 */
var MeasureMode;
(function (MeasureMode) {
    MeasureMode[MeasureMode["UNBOUNDED"] = 0] = "UNBOUNDED";
    MeasureMode[MeasureMode["EXACTLY"] = 1] = "EXACTLY";
    MeasureMode[MeasureMode["AT_MOST"] = 2] = "AT_MOST";
})(MeasureMode || (MeasureMode = {}));
/**
 * Any renderable entity that can be used in a widget hierarchy must be
 * measurable.
 *
 * @memberof PUXI
 * @interface IMeasurable
 */
/**
 * Measures its width & height based on the passed constraints.
 *
 * @memberof PUXI.IMeasurable#
 * @method onMeasure
 * @param {number} maxWidth
 * @param {number} maxHeight
 * @param {PUXI.MeasureMode} widthMode
 * @param {PUXI.MeasureMode} heightMode
 */
/**
 * @memberof PUXI.IMeasurable#
 * @method getMeasuredWidth
 * @returns {number} - the measured width of the entity after a `onMeasure` call
 */
/**
 * @memberof PUXI.IMeasurable#
 * @method getMeasuredHeight
 * @returns {number} - the measured height of the entity after a `onMeasure` call
 */

/**
 * An event manager handles the states related to certain events and can augment
 * widget interaction. For example, the click manager will hide clicks when
 * the object is dragging.
 *
 * Event managers are lifecycle objects - they can start/stop. Their constructor
 * will always accept one argument - the widget. Other settings can be applied before
 * `startEvent`.
 *
 * Ideally, you should access event managers _after_ your widget has initialized. This is
 * because it may depend on the widget's stage being assigned.
 *
 * @memberof PUXI
 * @class
 * @abstract
 */
class EventManager {
    /**
     * @param {Widget} target
     */
    constructor(target) {
        this.target = target;
        this.isEnabled = false; // use to track start/stopEvent
    }
    /**
     * @returns {Widget}
     */
    getTarget() {
        return this.target;
    }
}

/**
 * `ClickManager` handles hover and click events. It registers listeners
 * for `mousedown`, `mouseup`, `mousemove`, `mouseout`, `mouseover`, `touchstart`,
 * `touchend`, `touchendoutside`, `touchmove`, `rightup`, `rightdown`, `rightupoutside`
 * events.
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.EventManager
 */
class ClickManager extends EventManager {
    /**
     * @param {PUXI.Widget | PUXI.Button} target
     * @param {boolean}[includeHover=false] - enable hover (`mouseover`, `mouseout`) listeners
     * @param {boolean}[rightMouseButton=false] - use right mouse clicks
     * @param {boolean}[doubleClick=false] - fire double clicks
     */
    constructor(target, includeHover, rightMouseButton, doubleClick) {
        super(target);
        /**
         * @param {boolean}[includeHover]
         * @param {boolean}[rightMouseButton]
         * @param {boolean}[doubleClick]
         * @override
         */
        this.startEvent = (includeHover = this._includeHover, rightMouseButton = this._rightMouseButton, doubleClick = this._doubleClick) => {
            if (this.isEnabled) {
                return;
            }
            this._includeHover = includeHover;
            this.rightMouseButton = rightMouseButton;
            this._doubleClick = doubleClick;
            const { target } = this;
            target.insetContainer.on(this.evMouseDown, this.onMouseDownImpl);
            if (!this._rightMouseButton) {
                target.insetContainer.on('touchstart', this.onMouseDownImpl);
            }
            if (this._includeHover) {
                target.insetContainer.on('mouseover', this.onMouseOverImpl);
                target.insetContainer.on('mouseout', this.onMouseOutImpl);
            }
            this.isEnabled = true;
        };
        /**
         * @override
         */
        this.stopEvent = () => {
            if (!this.isEnabled) {
                return;
            }
            const { target } = this;
            if (this.bound) {
                target.insetContainer.removeListener(this.evMouseUp, this.onMouseUpImpl);
                target.insetContainer.removeListener(this.evMouseUpOutside, this.onMouseUpOutsideImpl);
                if (!this._rightMouseButton) {
                    target.insetContainer.removeListener('touchend', this.onMouseUpImpl);
                    target.insetContainer.removeListener('touchendoutside', this.onMouseUpOutsideImpl);
                }
                this.bound = false;
            }
            target.insetContainer.removeListener(this.evMouseDown, this.onMouseDownImpl);
            if (!this._rightMouseButton) {
                target.insetContainer.removeListener('touchstart', this.onMouseDownImpl);
            }
            if (this._includeHover) {
                target.insetContainer.removeListener('mouseover', this.onMouseOverImpl);
                target.insetContainer.removeListener('mouseout', this.onMouseOutImpl);
                target.insetContainer.removeListener('mousemove', this.onMouseMoveImpl);
                target.insetContainer.removeListener('touchmove', this.onMouseMoveImpl);
            }
            this.isEnabled = false;
        };
        this.onMouseDownImpl = (event) => {
            const { target: obj, evMouseUp, onMouseUpImpl: _onMouseUp, evMouseUpOutside, onMouseUpOutsideImpl: _onMouseUpOutside, _rightMouseButton: right, } = this;
            this.mouse.copyFrom(event.data.global);
            this.id = event.data.identifier;
            this.onPress.call(this.target, event, true);
            if (!this.bound) {
                obj.insetContainer.on(evMouseUp, _onMouseUp);
                obj.insetContainer.on(evMouseUpOutside, _onMouseUpOutside);
                if (!right) {
                    obj.insetContainer.on('touchend', _onMouseUp);
                    obj.insetContainer.on('touchendoutside', _onMouseUpOutside);
                }
                this.bound = true;
            }
            if (this._doubleClick) {
                const now = performance.now();
                if (now - this.time < 210) {
                    this.onClick.call(obj, event);
                }
                else {
                    this.time = now;
                }
            }
            event.data.originalEvent.preventDefault();
        };
        this.onMouseUpCommonImpl = (event) => {
            const { target: obj, evMouseUp, onMouseUpImpl: _onMouseUp, evMouseUpOutside, onMouseUpOutsideImpl: _onMouseUpOutside, } = this;
            if (event.data.identifier !== this.id) {
                return;
            }
            this.offset.set(event.data.global.x - this.mouse.x, event.data.global.y - this.mouse.y);
            if (this.bound) {
                obj.insetContainer.removeListener(evMouseUp, _onMouseUp);
                obj.insetContainer.removeListener(evMouseUpOutside, _onMouseUpOutside);
                if (!this._rightMouseButton) {
                    obj.insetContainer.removeListener('touchend', _onMouseUp);
                    obj.insetContainer.removeListener('touchendoutside', _onMouseUpOutside);
                }
                this.bound = false;
            }
            this.onPress.call(obj, event, false);
        };
        this.onMouseUpImpl = (event) => {
            if (event.data.identifier !== this.id) {
                return;
            }
            this.onMouseUpCommonImpl(event);
            // prevent clicks with scrolling/dragging objects
            if (this.target.dragThreshold) {
                this.movementX = Math.abs(this.offset.x);
                this.movementY = Math.abs(this.offset.y);
                if (Math.max(this.movementX, this.movementY) > this.target.dragThreshold) {
                    return;
                }
            }
            if (!this._doubleClick) {
                this.onClick.call(this.target, event);
            }
        };
        this.onMouseUpOutsideImpl = (event) => {
            if (event.data.identifier !== this.id) {
                return;
            }
            this.onMouseUpCommonImpl(event);
        };
        this.onMouseOverImpl = (event) => {
            if (!this.ishover) {
                this.ishover = true;
                this.target.insetContainer.on('mousemove', this.onMouseMoveImpl);
                this.target.insetContainer.on('touchmove', this.onMouseMoveImpl);
                this.onHover.call(this.target, event, true);
            }
        };
        this.onMouseOutImpl = (event) => {
            if (this.ishover) {
                this.ishover = false;
                this.target.insetContainer.removeListener('mousemove', this.onMouseMoveImpl);
                this.target.insetContainer.removeListener('touchmove', this.onMouseMoveImpl);
                this.onHover.call(this.target, event, false);
            }
        };
        this.onMouseMoveImpl = (event) => {
            this.onMove.call(this.target, event);
        };
        this.bound = false;
        this.id = 0;
        this.ishover = false;
        this.mouse = new Point();
        this.offset = new Point();
        this.movementX = 0;
        this.movementY = 0;
        this._includeHover = typeof includeHover === 'undefined' ? true : includeHover;
        this.rightMouseButton = typeof rightMouseButton === 'undefined' ? false : rightMouseButton;
        this._doubleClick = typeof doubleClick === 'undefined' ? false : doubleClick;
        target.interactive = true;
        this.time = 0;
        this.startEvent();
        this.onHover = () => null;
        this.onPress = () => null;
        this.onClick = () => null;
        this.onMove = () => null;
    }
    /**
     * Whether right mice are used for clicks rather than left mice.
     * @member boolean
     */
    get rightMouseButton() {
        return this._rightMouseButton;
    }
    set rightMouseButton(val) {
        this._rightMouseButton = val;
        this.evMouseDown = this._rightMouseButton ? 'rightdown' : 'mousedown';
        this.evMouseUp = this._rightMouseButton ? 'rightup' : 'mouseup';
        this.evMouseUpOutside = this._rightMouseButton ? 'rightupoutside' : 'mouseupoutside';
    }
}

/**
 * `DragManager` handles drag & drop events. It registers listeners for `mousedown`,
 * `touchstart` on the target and `mousemove`, `touchmove`, `mouseup`, `mouseupoutside`,
 * `touchend`, `touchendoutside` on the stage.
 *
 * By default, `draggable` widgets will internally handle drag-n-drop and reassigning
 * the callbacks on their `DragManager` will break their behaviour. You can prevent
 * this by using `eventBroker.dnd` directly without setting `widget.draggable` to
 * `true` (or using `widget.makeDraggable()`).
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.EventManager
 */
class DragManager extends EventManager {
    constructor(target) {
        super(target);
        this.onDragStartImpl = (e) => {
            const { target } = this;
            this.id = e.data.identifier;
            this.onPress(e, true);
            if (!this.isBound) {
                this.dragStart.copyFrom(e.data.global);
                target.stage.on('mousemove', this.onDragMoveImpl);
                target.stage.on('touchmove', this.onDragMoveImpl);
                target.stage.on('mouseup', this.onDragEndImpl);
                target.stage.on('mouseupoutside', this.onDragEndImpl);
                target.stage.on('touchend', this.onDragEndImpl);
                target.stage.on('touchendoutside', this.onDragEndImpl);
                target.stage.on('touchcancel', this.onDragEndImpl);
                this.isBound = true;
            }
            e.data.originalEvent.preventDefault();
        };
        this.onDragMoveImpl = (e) => {
            if (e.data.identifier !== this.id) {
                return;
            }
            const { lastCursor, dragOffset, dragStart, target, } = this;
            this.lastCursor.copyFrom(e.data.global);
            this.dragOffset.set(lastCursor.x - dragStart.x, lastCursor.y - dragStart.y);
            if (!this.isDragging) {
                this.movementX = Math.abs(dragOffset.x);
                this.movementY = Math.abs(dragOffset.y);
                if ((this.movementX === 0 && this.movementY === 0)
                    || Math.max(this.movementX, this.movementY) < target.dragThreshold) {
                    return; // threshold
                }
                if (target.dragRestrictAxis !== null) {
                    this.cancel = false;
                    if (target.dragRestrictAxis === 'x' && this.movementY > this.movementX) {
                        this.cancel = true;
                    }
                    else if (target.dragRestrictAxis === 'y' && this.movementY <= this.movementX) {
                        this.cancel = true;
                    }
                    if (this.cancel) {
                        this.onDragEndImpl(e);
                        return;
                    }
                }
                this.onDragStart(e);
                this.isDragging = true;
            }
            this.onDragMove(e, dragOffset);
        };
        this.onDragEndImpl = (e) => {
            if (e.data.identifier !== this.id) {
                return;
            }
            const { target } = this;
            if (this.isBound) {
                target.stage.removeListener('mousemove', this.onDragMoveImpl);
                target.stage.removeListener('touchmove', this.onDragMoveImpl);
                target.stage.removeListener('mouseup', this.onDragEndImpl);
                target.stage.removeListener('mouseupoutside', this.onDragEndImpl);
                target.stage.removeListener('touchend', this.onDragEndImpl);
                target.stage.removeListener('touchendoutside', this.onDragEndImpl);
                target.stage.removeListener('touchcancel', this.onDragEndImpl);
                this.isDragging = false;
                this.isBound = false;
                this.onDragEnd(e);
                this.onPress(e, false);
            }
        };
        this.isBound = false;
        this.isDragging = false;
        this.id = 0;
        this.dragStart = new Point();
        this.dragOffset = new Point();
        this.lastCursor = new Point();
        this.movementX = 0;
        this.movementY = 0;
        this.cancel = false;
        this.target.interactive = true;
        this.onPress = () => null;
        this.onDragStart = () => null;
        this.onDragMove = () => null;
        this.onDragEnd = () => null;
        this.startEvent();
    }
    startEvent() {
        if (this.isEnabled) {
            return;
        }
        const { target } = this;
        target.insetContainer.on('mousedown', this.onDragStartImpl);
        target.insetContainer.on('touchstart', this.onDragStartImpl);
        this.isEnabled = true;
    }
    stopEvent() {
        if (!this.isEnabled) {
            return;
        }
        const { target } = this;
        if (this.isBound) {
            target.stage.removeListener('mousemove', this.onDragMoveImpl);
            target.stage.removeListener('touchmove', this.onDragMoveImpl);
            target.stage.removeListener('mouseup', this.onDragEndImpl);
            target.stage.removeListener('mouseupoutside', this.onDragEndImpl);
            target.stage.removeListener('touchend', this.onDragEndImpl);
            target.stage.removeListener('touchendoutside', this.onDragEndImpl);
            this.isBound = false;
        }
        target.insetContainer.removeListener('mousedown', this.onDragStartImpl);
        target.insetContainer.removeListener('touchstart', this.onDragStartImpl);
        this.isEnabled = false;
    }
}

/**
 * The event brokers allows you to access event managers without manually assigning
 * them to a widget. By default, the click (`PUXI.ClickManager`), dnd (`PUXI.DragManager`)
 * are defined. You can add event managers for all (new) widgets by adding an entry to
 * `EventBroker.MANAGER_MAP`.
 *
 * @memberof PUXI
 * @class
 */
class EventBroker {
    constructor(target) {
        this.target = target;
        for (const mgr of Object.keys(EventBroker.MANAGER_MAP)) {
            Object.defineProperty(this, mgr, {
                get() {
                    if (!this[`_${mgr}`]) {
                        this[`_${mgr}`] = new EventBroker.MANAGER_MAP[mgr](this.target);
                    }
                    return this[`_${mgr}`];
                },
            });
        }
    }
}
EventBroker.MANAGER_MAP = {
    click: ClickManager,
    dnd: DragManager,
};

/**
 * Handles the `wheel` and `scroll` DOM events on widgets. It also registers
 * listeners for `mouseout` and `mouseover`.
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.EventManager
 */
class ScrollManager extends EventManager {
    constructor(target, preventDefault = true) {
        super(target);
        this.onMouseScrollImpl = (e) => {
            const { target, preventDefault, delta } = this;
            if (preventDefault) {
                event.preventDefault();
            }
            if (typeof e.deltaX !== 'undefined') {
                delta.set(e.deltaX, e.deltaY);
            }
            else // Firefox
             {
                delta.set(e.axis === 1 ? e.detail * 60 : 0, e.axis === 2 ? e.detail * 60 : 0);
            }
            this.onMouseScroll.call(target, event, delta);
        };
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        this.onHoverImpl = (e) => {
            const { onMouseScrollImpl } = this;
            if (!this.bound) {
                document.addEventListener('mousewheel', onMouseScrollImpl, false);
                document.addEventListener('DOMMouseScroll', onMouseScrollImpl, false);
                this.bound = true;
            }
        };
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        this.onMouseOutImpl = (e) => {
            const { onMouseScrollImpl } = this;
            if (this.bound) {
                document.removeEventListener('mousewheel', onMouseScrollImpl);
                document.removeEventListener('DOMMouseScroll', onMouseScrollImpl);
                this.bound = false;
            }
        };
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        this.onMouseScroll = function onMouseScroll(event, delta) {
            // Default onMouseScroll.
        };
        this.bound = false;
        this.delta = new Point();
        this.preventDefault = preventDefault;
        this.startEvent();
    }
    /**
     * @override
     */
    startEvent() {
        const { target, onHoverImpl, onMouseOutImpl } = this;
        target.contentContainer.on('mouseover', onHoverImpl);
        target.contentContainer.on('mouseout', onMouseOutImpl);
    }
    /**
     * @override
     */
    stopEvent() {
        const { target, onMouseScrollImpl, onHoverImpl, onMouseOutImpl } = this;
        if (this.bound) {
            document.removeEventListener('mousewheel', onMouseScrollImpl);
            document.removeEventListener('DOMMouseScroll', onMouseScrollImpl);
            this.bound = false;
        }
        target.contentContainer.removeListener('mouseover', onHoverImpl);
        target.contentContainer.removeListener('mouseout', onMouseOutImpl);
    }
}

/**
 * A widget is a user interface control that renders content inside its prescribed
 * rectangle on the screen.
 *
 * @memberof PUXI
 * @class
 * @extends PIXI.utils.EventEmitter
 * @implements PUXI.IMeasurable
 */
class Widget extends utils.EventEmitter {
    constructor() {
        super();
        /**
         * This container owns the background + content of this widget.
         * @member {PIXI.Container}
         * @readonly
         */
        this.insetContainer = new Container();
        /**
         * This container holds the content of this widget. Subclasses should add
         * renderable display-objects to this container.
         * @member {PIXI.Container}
         * @readonly
         */
        this.contentContainer = this.insetContainer.addChild(new Container());
        /**
         * Children of this widget. Use `WidgetGroup` to position children.
         * @member {PUXI.Widget[]}
         * @readonly
         */
        this.widgetChildren = [];
        /**
         * Stage whose scene graph holds this widget. Once set, this cannot be changed.
         * @member {PUXI.Stage}
         * @readonly
         */
        this.stage = null;
        /**
         * Layout insets of this widget. In normal state, the widget should be in this
         * rectangle inside the parent reference frame.
         * @member {PUXI.Insets}
         * @readonly
         */
        this.layoutMeasure = new Insets();
        this.initialized = false;
        this.dragInitialized = false;
        this.dropInitialized = false;
        this.dirty = true;
        this._oldWidth = -1;
        this._oldHeight = -1;
        this.pixelPerfect = true;
        this._paddingLeft = 0;
        this._paddingTop = 0;
        this._paddingRight = 0;
        this._paddingBottom = 0;
        this._elevation = 0;
        this.tint = 0;
        this.blendMode = BLEND_MODES.NORMAL;
        this.draggable = false;
        this.droppable = false;
    }
    /**
     * Update method that is to be overriden. This is called before a `render()`
     * pass on widgets that are dirty.
     *
     * @private
     */
    update() {
        if (this._layoutDirty) {
            console.log('here');
            setTimeout(() => {
                if (this._layoutDirty) {
                    this.stage.measureAndLayout();
                }
            }, 0);
        }
    }
    /**
     * The measured width that is used by the parent's layout manager to place this
     * widget.
     * @member {number}
     */
    get measuredWidth() {
        return this._measuredWidth;
    }
    /**
     * The measured height that is used by the parent's layout manager to place this
     * widget.
     * @member {number}
     */
    get measuredHeight() {
        return this._measuredHeight;
    }
    /**
     * Alias for `Widget.measuredWidth`.
     *
     * @returns {number} the measured width
     */
    getMeasuredWidth() {
        return this._measuredWidth;
    }
    /**
     * Alias for `Widget.measuredHeight`.
     *
     * @returns {number} the measured height
     */
    getMeasuredHeight() {
        return this._measuredHeight;
    }
    /**
     * Override this method to measure the dimensions for your widget. By default, it
     * will use the natural width/height of this widget's content (`contentContainer`)
     * plus any padding.
     *
     * @param {number} width - width of region provided by parent
     * @param {number} height - height of region provided by parent
     * @param {PUXI.MeasureMode} widthMode - mode in which provided width is to be used
     * @param {PUXI.MeasureMode} heightMode - mode in which provided height is to be used
     */
    onMeasure(width, height, widthMode, heightMode) {
        const naturalWidth = this.contentContainer.width + this.paddingHorizontal;
        const naturalHeight = this.contentContainer.height + this.paddingVertical;
        switch (widthMode) {
            case MeasureMode.EXACTLY:
                this._measuredWidth = width;
                break;
            case MeasureMode.UNBOUNDED:
                this._measuredWidth = naturalWidth;
                break;
            case MeasureMode.AT_MOST:
                this._measuredWidth = Math.min(width, naturalWidth);
                break;
        }
        switch (heightMode) {
            case MeasureMode.EXACTLY:
                this._measuredHeight = height;
                break;
            case MeasureMode.UNBOUNDED:
                this._measuredHeight = naturalHeight;
                break;
            case MeasureMode.AT_MOST:
                this._measuredHeight = Math.min(height, naturalHeight);
                break;
        }
    }
    /**
     * This method calls `Widget.onMeasure` and should not be overriden. It does internal
     * bookkeeping.
     *
     * @param {number} width
     * @param {number} height
     * @param {PUXI.MeasureMode} widthMode
     * @param {PUXI.MeasureMode} heightMode
     */
    measure(width, height, widthMode, heightMode) {
        this.onMeasure(width, height, widthMode, heightMode);
    }
    /**
     * This method should set the frame in which rendering will occur and lay out
     * child widgets in that frame.
     *
     * @param l
     * @param t
     * @param r
     * @param b
     * @param dirty
     * @protected
     */
    onLayout(l, t = l, r = l, b = t, dirty = true) {
        this.layoutMeasure.left = l;
        this.layoutMeasure.top = t;
        this.layoutMeasure.right = r;
        this.layoutMeasure.bottom = b;
        this._width = r - l;
        this._height = b - t;
        if (this.background) {
            this.background.x = 0;
            this.background.y = 0;
            this.background.width = r - l;
            this.background.height = b - t;
        }
        // Update parallel PIXI node too!
        this.insetContainer.x = l;
        this.insetContainer.y = t;
        this.contentContainer.x = this._paddingLeft;
        this.contentContainer.y = this._paddingTop;
        // Don't set width/height on inset, content because that would scale
        // the contents (we don't want that).
        this._layoutDirty = false;
    }
    layout(l, t = l, r = l, b = t, dirty = true) {
        this.onLayout(l, t, r, b, dirty);
    }
    /**
     * Use this to specify how you want to layout this widget w.r.t its parent.
     * The specific layout options class depends on which layout you are
     * using in the parent widget.
     *
     * @param {PUXI.LayoutOptions} lopt
     * @returns {Widget} this
     */
    setLayoutOptions(lopt) {
        this.layoutOptions = lopt;
        return this;
    }
    /**
     * The event broker for this widget that holds all the event managers. This can
     * be used to start/stop clicks, drags, scrolls and configure how those events
     * are handled/interpreted.
     * @member PUXI.EventBroker
     */
    get eventBroker() {
        if (!this._eventBroker) {
            this._eventBroker = new EventBroker(this);
        }
        return this._eventBroker;
    }
    /**
     * Padding on left side.
     * @member {number}
     */
    get paddingLeft() {
        return this._paddingLeft;
    }
    set paddingLeft(val) {
        this._paddingLeft = val;
        this.dirty = true;
    }
    /**
     * Padding on top side.
     * @member {number}
     */
    get paddingTop() {
        return this._paddingTop;
    }
    set paddingTop(val) {
        this._paddingTop = val;
        this.dirty = true;
    }
    /**
     * Padding on right side.
     * @member {number}
     */
    get paddingRight() {
        return this._paddingRight;
    }
    set paddingRight(val) {
        this._paddingRight = val;
        this.dirty = true;
    }
    /**
     * Padding on bottom side.
     * @member {number}
     */
    get paddingBottom() {
        return this._paddingBottom;
    }
    set paddingBottom(val) {
        this._paddingBottom = val;
        this.dirty = true;
    }
    /**
     * Sum of left & right padding.
     * @member {number}
     * @readonly
     */
    get paddingHorizontal() {
        return this._paddingLeft + this._paddingRight;
    }
    /**
     * Sum of top & bottom padding.
     * @member {number}
     * @readonly
     */
    get paddingVertical() {
        return this._paddingTop + this._paddingBottom;
    }
    /**
     * Whether this widget is interactive in the PixiJS scene graph.
     * @member {boolean}
     */
    get interactive() {
        return this.insetContainer.interactive;
    }
    set interactive(val) {
        this.insetContainer.interactive = true;
        this.contentContainer.interactive = true;
    }
    /**
     * Layout width of this widget.
     * @member {number}
     * @readonly
     */
    get width() {
        return this._width;
    }
    /**
     * Layout height of this widget.
     * @member {number}
     * @readonly
     */
    get height() {
        return this._height;
    }
    /**
     * Layout width of this widget's content, which is the width minus horizontal padding.
     * @member {number}
     * @readonly
     */
    get contentWidth() {
        return this._width - this.paddingHorizontal;
    }
    /**
     * Layout height of this widget's content, which is the height minus vertical padding.
     * @member {number}
     * @readonly
     */
    get contentHeight() {
        return this._height - this.paddingVertical;
    }
    /**
     * Alpha of this widget & its contents.
     * @member {number}
     */
    get alpha() {
        return this.insetContainer.alpha;
    }
    set alpha(val) {
        this.insetContainer.alpha = val;
    }
    /**
     * Sets the padding values.
     *
     * To set all paddings to one value:
     * ```
     * widget.setPadding(8);
     * ```
     *
     * To set horizontal & vertical padding separately:
     * ```
     * widget.setPadding(4, 12);
     * ```
     *
     * @param {number}[l=0] - left padding
     * @param {number}[t=l] - top padding (default is equal to left padding)
     * @param {number}[r=l] - right padding (default is equal to right padding)
     * @param {number}[b=t] - bottom padding (default is equal to top padding)
     */
    setPadding(l, t = l, r = l, b = t) {
        this._paddingLeft = l;
        this._paddingTop = t;
        this._paddingRight = r;
        this._paddingBottom = b;
        this.dirty = true;
        return this;
    }
    /**
     * @returns {PIXI.Container} - the background display-object
     */
    getBackground() {
        return this.background;
    }
    /**
     * The background of a widget is a `PIXI.DisplayObject` that is rendered before
     * all of its children.
     *
     * @param {PIXI.Container | number | string} bg - the background display-object or
     *     a color that will be used to generate a `PIXI.Graphics` as the background.
     */
    setBackground(bg) {
        if (this.background) {
            this.insetContainer.removeChild(this.background);
        }
        if (typeof bg === 'string') {
            bg = utils.string2hex(bg);
        }
        if (typeof bg === 'number') {
            bg = new Graphics()
                .beginFill(bg)
                .drawRect(0, 0, 1, 1)
                .endFill();
        }
        this.background = bg;
        if (bg) {
            bg.width = this.width;
            bg.height = this.height;
            this.insetContainer.addChildAt(bg, 0);
        }
        return this;
    }
    /**
     * @returns {number} the alpha on the background display-object.
     */
    getBackgroundAlpha() {
        return this.background ? this.background.alpha : 1;
    }
    /**
     * This can be used to set the alpha on the _background_ of this widget. This
     * does not affect the widget's contents nor individual components of the
     * background display-object.
     *
     * @param {number} val - background alpha
     */
    setBackgroundAlpha(val) {
        if (!this.background) {
            this.setBackground(0xffffff);
        }
        this.background.alpha = val;
        return this;
    }
    /**
     * @return {number} the elevation set on this widget
     */
    getElevation() {
        return this._elevation;
    }
    /**
     * This can be used add a drop-shadow that will appear to raise this widget by
     * the given elevation against its parent.
     *
     * @param {number} val - elevation to use. 2px is good for most widgets.
     */
    setElevation(val) {
        this._elevation = val;
        if (val === 0 && this._dropShadow) {
            const i = this.insetContainer.filters.indexOf(this._dropShadow);
            if (i > 0) {
                this.insetContainer.filters.splice(i, 1);
            }
        }
        else if (val > 0) {
            if (!this._dropShadow) {
                if (!this.insetContainer.filters) {
                    this.insetContainer.filters = [];
                }
                this._dropShadow = new DropShadowFilter({ distance: val });
                this.insetContainer.filters.push(this._dropShadow);
            }
            this._dropShadow.distance = val;
        }
        return this;
    }
    /**
     * Will trigger a full layout pass next animation frame.
     */
    requestLayout() {
        this._layoutDirty = true;
    }
    /**
     * Adds the widgets as children of this one.
     *
     * @param {PUXI.Widget[]} widgets
     * @returns {Widget} - this widget
     */
    addChild(...widgets) {
        for (let i = 0; i < widgets.length; i++) {
            const widget = widgets[i];
            if (widget.parent) {
                widget.parent.removeChild(widget);
            }
            widget.parent = this;
            this.contentContainer.addChild(widget.insetContainer);
            this.widgetChildren.push(widget);
        }
        return this;
    }
    /**
     * Orphans the widgets that are children of this one.
     *
     * @param {PUXI.Widget[]} widgets
     * @returns {Widget} - this widget
     */
    removeChild(...widgets) {
        for (let i = 0; i < widgets.length; i++) {
            const widget = widgets[i];
            const index = this.widgetChildren.indexOf(widget);
            if (index !== -1) {
                widget.insetContainer.parent.removeChild(widget.insetContainer);
                this.widgetChildren.splice(index, 1);
                widget.parent = null;
            }
        }
        return this;
    }
    /**
     * Makes this widget `draggable`.
     */
    makeDraggable() {
        this.draggable = true;
        if (this.initialized) {
            this.initDraggable();
        }
        return this;
    }
    /**
     * Makes this widget not `draggable`.
     */
    clearDraggable() {
        if (this.dragInitialized) {
            this.dragInitialized = false;
            this.eventBroker.dnd.stopEvent();
        }
    }
    /**
     * Widget initialization related to the stage. This method should always call
     * `super.initialize()`.
     *
     * This method expects `stage` to be set before calling it. This is handled
     * by the `Stage` itself.
     *
     * This will set `initialized` to true. If it was already set to true, it _should
     * do nothing_.
     *
     * @protected
     */
    initialize() {
        if (this.initialized) {
            return;
        }
        if (this.draggable) {
            this.initDraggable();
        }
        if (this.droppable) {
            this.initDroppable();
        }
        this.initialized = true;
    }
    initDraggable() {
        if (this.dragInitialized) {
            return;
        }
        this.dragInitialized = true;
        const realPosition = new Point();
        const dragPosition = new Point();
        const dnd = this.eventBroker.dnd;
        const { insetContainer } = this;
        dnd.onDragStart = (e) => {
            const added = DragDropController.add(this, e);
            if (!this.isDragging && added) {
                this.isDragging = true;
                insetContainer.interactive = false;
                realPosition.copyFrom(insetContainer.position);
                this.emit('draggablestart', e);
            }
        };
        dnd.onDragMove = (e, offset) => {
            if (this.isDragging) {
                dragPosition.set(realPosition.x + offset.x, realPosition.y + offset.y);
                insetContainer.x = dragPosition.x;
                insetContainer.y = dragPosition.y;
                this.emit('draggablemove', e);
            }
        };
        dnd.onDragEnd = (e) => {
            if (this.isDragging) {
                this.isDragging = false;
                DragDropController.getItem(this);
                // Return to container after 0ms if not picked up by a droppable
                setTimeout(() => {
                    this.insetContainer.interactive = true;
                    this.insetContainer.position.copyFrom(realPosition);
                    this.emit('draggableend', e);
                }, 0);
            }
        };
    }
    /**
     * Makes this widget `droppable`.
     */
    makeDroppable() {
        this.droppable = true;
        if (this.initialized) {
            this.initDroppable();
        }
        return this;
    }
    /**
     * Makes this widget not `droppable`.
     */
    clearDroppable() {
        if (this.dropInitialized) {
            this.dropInitialized = false;
            this.contentContainer.removeListener('mouseup', this.onDrop);
            this.contentContainer.removeListener('touchend', this.onDrop);
        }
    }
    initDroppable() {
        if (!this.dropInitialized) {
            this.dropInitialized = true;
            const container = this.contentContainer;
            this.contentContainer.interactive = true;
            this.onDrop = (event) => {
                const item = DragDropController.getEventItem(event, this.dropGroup);
                if (item && item.isDragging) {
                    item.isDragging = false;
                    item.insetContainer.interactive = true;
                    const parent = this.droppableReparent !== null ? this.droppableReparent : self;
                    parent.container.toLocal(item.container.position, item.container.parent, item);
                    if (parent.container != item.container.parent) {
                        parent.addChild(item);
                    }
                }
            };
            container.on('mouseup', this.onDrop);
            container.on('touchend', this.onDrop);
        }
    }
    /**
     * Creates a widget that holds the display-object as its content. If `content` is
     * a `PUXI.Widget`, then it will be returned.
     * @param {PIXI.Container | Widget} content
     * @static
     */
    static fromContent(content) {
        if (content instanceof Widget) {
            return content;
        }
        const widget = new Widget();
        widget.contentContainer.addChild(content);
        return widget;
    }
    /**
     * Easy utility to resolve measured dimension.
     * @param {number} natural - your widget's natural dimension
     * @param {number} limit - width/height limit passed by parent
     * @param {PUXI.MeasureMode} mode - measurement mode passed by parent
     */
    static resolveMeasuredDimen(natural, limit, mode) {
        if (mode === MeasureMode.EXACTLY) {
            return limit;
        }
        else if (mode === MeasureMode.AT_MOST) {
            return Math.min(limit, natural);
        }
        return natural;
    }
}

/**
 * Alignments supported by layout managers in PuxiJS core.
 *
 * @memberof PUXI
 * @enum
 */
var ALIGN;
(function (ALIGN) {
    ALIGN[ALIGN["LEFT"] = 0] = "LEFT";
    ALIGN[ALIGN["TOP"] = 0] = "TOP";
    ALIGN[ALIGN["MIDDLE"] = 4081] = "MIDDLE";
    ALIGN[ALIGN["CENTER"] = 4081] = "CENTER";
    ALIGN[ALIGN["RIGHT"] = 1048561] = "RIGHT";
    ALIGN[ALIGN["BOTTOM"] = 1048561] = "BOTTOM";
    ALIGN[ALIGN["NONE"] = 4294967295] = "NONE";
})(ALIGN || (ALIGN = {}));

/**
 * This are the base constraints that you can apply on a `PUXI.Widget` under any
 * layout manager. It specifies the dimensions of a widget, while the position
 * of the widget is left to the parent to decide. If a dimension (width or height)
 * is set to a value between -1 and 1, then it is interpreted as a percentage
 * of the parent's dimension.
 *
 * The following example will render a widget at 50% of the parent's width and 10px height:
 *
 * ```js
 * const widget = new PUXI.Widget();
 * const parent = new PUXI.Widget();
 *
 * widget.layoutOptions = new PUXI.LayoutOptions(
 *      .5,
 *      10
 * );
 * parent.addChild(widget);
 * ```
 *
 * @memberof PUXI
 * @class
 */
class LayoutOptions {
    /**
     * @param {number}[width = LayoutOptions.WRAP_CONTENT]
     * @param {number}[height = LayoutOptions.WRAP_CONTENT]
     */
    constructor(width = LayoutOptions.WRAP_CONTENT, height = LayoutOptions.WRAP_CONTENT) {
        /**
         * Preferred width of the widget in pixels. If its value is between -1 and 1, it
         * is interpreted as a percentage of the parent's width.
         * @member {number}
         * @default {PUXI.LayoutOptions.WRAP_CONTENT}
         */
        this.width = width;
        /**
         * Preferred height of the widget in pixels. If its value is between -1 and 1, it
         * is interpreted as a percentage of the parent's height.
         * @member {number}
         * @default {PUXI.LayoutOptions.WRAP_CONTENT}
         */
        this.height = height;
        this.markers = {};
    }
    /**
     * The left margin in pixels of the widget.
     * @member {number}
     * @default 0
     */
    get marginLeft() {
        return this._marginLeft || 0;
    }
    set marginLeft(val) {
        this._marginLeft = val;
    }
    /**
     * This top margin in pixels of the widget.
     * @member {number}
     * @default 0
     */
    get marginTop() {
        return this._marginTop || 0;
    }
    set marginTop(val) {
        this._marginTop = val;
    }
    /**
     * The right margin in pixels of the widget.
     * @member {number}
     * @default 0
     */
    get marginRight() {
        return this._marginRight || 0;
    }
    set marginRight(val) {
        this._marginRight = val;
    }
    /**
     * The bottom margin in pixels of the widget.
     * @member {number}
     * @default 0
     */
    get marginBottom() {
        return this._marginBottom || 0;
    }
    set marginBottom(val) {
        this._marginBottom = val;
    }
    setMargin(left, top, right, bottom) {
        this._marginLeft = left;
        this._marginTop = top;
        this._marginRight = right;
        this._marginBottom = bottom;
    }
}
LayoutOptions.FILL_PARENT = 0xfffffff1;
LayoutOptions.WRAP_CONTENT = 0xfffffff2;
LayoutOptions.MAX_DIMEN = 0xfffffff0;
LayoutOptions.DEFAULT = new LayoutOptions();

/**
 * @memberof PUXI
 * @interface IAnchorLayoutParams
 * @property {number} anchorLeft - distance from parent's left inset to child's left edge
 * @property {number} anchorTop - distance from parent's top inset to child's top edge
 * @property {number} anchorRight - distance from parent's right inset to child's right edge
 * @property {number} anchorBottom - distance from parent's bottom insets to child's bottom edge
 * @property {PUXI.ALIGN} horizontalAlign - horizontal alignment of child in anchor region
 * @property {PUXI.ALIGN} verticalAlign - vertical alignment of child in anchor region
 */
/**
 * Anchors the edge of a widget to defined offsets from the parent's insets.
 *
 * The following example will render a widget at (10px, 15%) with a width extending
 * to the parent's center and a height extending till 40px above the parent's bottom
 * inset.
 * ```js
 * new PUXI.AnchoredLayoutOptions({
 *      anchorLeft: 10,
 *      anchorTop: .15,
 *      anchorRight: .5,
 *      anchorBottom: 40
 * });
 * ```
 *
 * You can specify how the widget should be aligned in the anchor region using the
 * `horizontalAlign` and `verticalAlign` properties.
 *
 * The width & height of widgets must be `WRAP_CONTENT` or `FILL_PARENT`. Using the latter
 * would make the child fill the anchor region.
 *
 * @memberof PUXI
 * @extends PUXI.LayoutOptions
 * @class
 */
class AnchorLayoutOptions extends LayoutOptions {
    constructor(options) {
        super(LayoutOptions.WRAP_CONTENT, LayoutOptions.WRAP_CONTENT);
        this.anchorLeft = options.anchorLeft || 0;
        this.anchorTop = options.anchorTop || 0;
        this.anchorBottom = options.anchorBottom || 0;
        this.anchorRight = options.anchorRight || 0;
        this.horizontalAlign = options.horizontalAlign || ALIGN.LEFT;
        this.verticalAlign = options.verticalAlign || ALIGN.CENTER;
    }
}

/**
 * @memberof PUXI
 * @interface
 * @property {number} width
 * @property {number} height
 * @property {number} x
 * @property {number} y
 * @property {PIXI.Point} anchor
 */
/**
 * `PUXI.FastLayoutOptions` is an extension to `PUXI.LayoutOptions` that also
 * defines the x & y coordinates. It is accepted by the stage and `PUXI.FastLayout`.
 *
 * If x or y is between -1 and 1, then that dimension will be interpreted as a
 * percentage of the parent's width or height.
 *
 * @memberof PUXI
 * @extends PUXI.LayoutOptions
 * @class
 */
class FastLayoutOptions extends LayoutOptions {
    constructor(options) {
        super(options.width, options.height);
        /**
         * X-coordinate of the widget in its parent's reference frame. If it is
         * absolutely less than 1, then it will be interpreted as a percent of
         * the parent's width.
         * @member {number}
         * @default 0
         */
        this.x = options.x || 0;
        /**
         * Y-coordinate of the widget in its parent's reference frame. If it is
         * absolutely less than 1, then it will be interpreted as a percent of
         * the parent's height.
         * @member {number}
         * @default 0
         */
        this.y = options.y || 0;
        /**
         * The anchor is a normalized point telling where the (x,y) position of the
         * widget lies inside of it. By default, it is (0, 0), which means that the
         * top-left corner of the widget will be at (x,y); however, setting it to
         * (.5,.5) will make the _center of the widget_ be at (x,y) in the parent's
         * reference frame.
         * @member {PIXI.Point}
         * @default PUXI.FastLayoutOptions.DEFAULT_ANCHOR
         */
        this.anchor = options.anchor || FastLayoutOptions.DEFAULT_ANCHOR.clone();
    }
}
FastLayoutOptions.DEFAULT_ANCHOR = new Point(0, 0);
FastLayoutOptions.CENTER_ANCHOR = new Point(0.5, 0.5); // fragile, shouldn't be modified

/**
 * @memberof PUXI
 * @interface IBorderLayoutParams
 * @property {number} width
 * @property {number} height
 * @property {number} region
 * @property {number} horizontalAlign
 * @property {number} verticalAlign
 */
/**
 * `PUXI.BorderLayoutOptions` defines a simple layout with five regions - the center and
 * four regions along each border. The top and bottom regions span the full width of
 * the parent widget-group. The left and right regions span the height of the layout
 * minus the top and bottom region heights.
 *
 * ```
 * ------------------------------------------------
 * |                 TOP REGION                   |
 * ------------------------------------------------
 * |        |                            |        |
 * |  LEFT  |          CENTER            | RIGHT  |
 * | REGION |          REGION            | REGION |
 * |        |                            |        |
 * ------------------------------------------------
 * |                BOTTOM REGION                 |
 * ------------------------------------------------
 * ```
 *
 * The height of the layout is measured as the sum of the heights of the top, center, and bottom
 * regions. Similarly, the width of the layout is measured as the width of the left, center, and
 * right regions.
 *
 * As of now, border layout doesn't support percent widths and heights.
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.LayoutOptions
 */
class BorderLayoutOptions extends LayoutOptions {
    constructor(options) {
        super(options.width, options.height);
        /**
         * The border along which the widget is to be placed. This can be one of `POS_LEFT`,
         * `POS_TOP`, `POS_RIGHT`, `POS_BOTTOM`.
         * @member {number}
         * @default {PUXI.BorderLayoutOptions#REGION_CENTER}
         */
        this.region = options.region || BorderLayoutOptions.REGION_CENTER;
        /**
         * Alignment of the widget horizontally in its region.
         * @member {PUXI.ALIGN}
         * @default {PUXI.ALIGN.LEFT}
         */
        this.horizontalAlign = options.horizontalAlign || ALIGN.LEFT;
        /**
         * Alignment of the widget vertically in its region.
         * @member {PUXI.ALIGN}
         * @default {PUXI.ALIGN.TOP}
         */
        this.verticalAlign = options.verticalAlign || ALIGN.TOP;
    }
}
/**
 * Positions a widget inside the left border of the layout.
 * @static
 * @member {number}
 */
BorderLayoutOptions.REGION_LEFT = 0xeff1;
/**
 * Positions a widget below the top border of the layout.
 * @static
 * @member {number}
 */
BorderLayoutOptions.REGION_TOP = 0xeff2;
/**
 * Positions a widget below the right border of the layout.
 * @static
 * @member {number}
 */
BorderLayoutOptions.REGION_RIGHT = 0xeff3;
/**
 * Positions a widget below the top border of the layout.
 * @static
 * @member {number}
 */
BorderLayoutOptions.REGION_BOTTOM = 0xeff4;
/**
 * Positions a widget in the center of the layout. The main content of the layout
 * should be in the center.
 * @static
 * @member {number}
 */
BorderLayoutOptions.REGION_CENTER = 0xeff5;

/**
 * `PUXI.FastLayout` is used in conjunction with `PUXI.FastLayoutOptions`. It is the
 * default layout for most widget groups.
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.ILayoutManager
 * @example
 * ```
 * parent.useLayout(new PUXI.FastLayout())
 * ```
 */
class FastLayout {
    onAttach(host) {
        this.host = host;
    }
    onDetach() {
        this.host = null;
    }
    onMeasure(maxWidth, maxHeight, widthMode, heightMode) {
        // TODO: Passthrough optimization pass, if there is only one child with FILL_PARENT width or height
        // then don't measure twice.
        this._measuredWidth = maxWidth;
        this._measuredHeight = maxHeight;
        const children = this.host.widgetChildren;
        // Measure children
        for (let i = 0, j = children.length; i < j; i++) {
            const widget = children[i];
            const lopt = (widget.layoutOptions || LayoutOptions.DEFAULT);
            const widthMeasureMode = this.getChildMeasureMode(lopt.width, widthMode);
            const heightMeasureMode = this.getChildMeasureMode(lopt.height, heightMode);
            const loptWidth = (Math.abs(lopt.width) < 1) ? lopt.width * maxWidth : lopt.width;
            const loptHeight = (Math.abs(lopt.height) < 1) ? lopt.height * maxHeight : lopt.height;
            widget.measure(widthMeasureMode === MeasureMode.EXACTLY ? loptWidth : maxWidth, heightMeasureMode === MeasureMode.EXACTLY ? loptHeight : maxHeight, widthMeasureMode, heightMeasureMode);
        }
        this._measuredWidth = this.measureWidthReach(maxWidth, widthMode);
        this._measuredHeight = this.measureHeightReach(maxHeight, heightMode);
        this.measureChildFillers();
    }
    getChildMeasureMode(dimen, parentMeasureMode) {
        if (parentMeasureMode === MeasureMode.UNBOUNDED) {
            return MeasureMode.UNBOUNDED;
        }
        if (dimen === LayoutOptions.FILL_PARENT || dimen === LayoutOptions.WRAP_CONTENT) {
            return MeasureMode.AT_MOST;
        }
        return MeasureMode.EXACTLY;
    }
    measureWidthReach(parentWidthLimit, widthMode) {
        if (widthMode === MeasureMode.EXACTLY) {
            return parentWidthLimit;
        }
        const children = this.host.widgetChildren;
        let measuredWidth = 0;
        for (let i = 0, j = children.length; i < j; i++) {
            const widget = children[i];
            const childWidth = widget.getMeasuredWidth();
            const lopt = (widget.layoutOptions || LayoutOptions.DEFAULT);
            const x = lopt.x ? lopt.x : 0;
            const anchor = lopt.anchor ? lopt.anchor : FastLayoutOptions.DEFAULT_ANCHOR;
            // If lopt.x is %, then (1 - lopt.x)% of parent width should be as large
            // as (1 - anchor.x)% child's width.
            const minr = (Math.abs(x) < 1 ? (1 - anchor.x) * childWidth / (1 - x) : x);
            measuredWidth = Math.max(measuredWidth, minr);
        }
        if (widthMode === MeasureMode.AT_MOST) {
            measuredWidth = Math.min(parentWidthLimit, measuredWidth);
        }
        return measuredWidth;
    }
    measureHeightReach(parentHeightLimit, heightMode) {
        if (heightMode === MeasureMode.EXACTLY) {
            return parentHeightLimit;
        }
        const children = this.host.widgetChildren;
        let measuredHeight = 0;
        for (let i = 0, j = children.length; i < j; i++) {
            const widget = children[i];
            const childHeight = widget.getMeasuredHeight();
            const lopt = (widget.layoutOptions || LayoutOptions.DEFAULT);
            const y = lopt.y ? lopt.y : 0;
            const anchor = lopt.anchor ? lopt.anchor : FastLayoutOptions.DEFAULT_ANCHOR;
            const minb = (Math.abs(y) < 1 ? (1 - anchor.y) * childHeight / (1 - y) : y);
            measuredHeight = Math.max(measuredHeight, minb);
        }
        if (heightMode === MeasureMode.AT_MOST) {
            measuredHeight = Math.min(parentHeightLimit, measuredHeight);
        }
        return measuredHeight;
    }
    measureChildFillers() {
        const children = this.host.widgetChildren;
        for (let i = 0, j = children.length; i < j; i++) {
            const widget = children[i];
            const lopt = (widget.layoutOptions || LayoutOptions.DEFAULT);
            if (lopt.width === LayoutOptions.FILL_PARENT || lopt.height === LayoutOptions.FILL_PARENT) {
                const widthMode = lopt.width === LayoutOptions.FILL_PARENT ? MeasureMode.EXACTLY : MeasureMode.AT_MOST;
                const heightMode = lopt.height === LayoutOptions.FILL_PARENT ? MeasureMode.EXACTLY : MeasureMode.AT_MOST;
                widget.measure(this._measuredWidth, this._measuredHeight, widthMode, heightMode);
            }
        }
    }
    onLayout() {
        const parent = this.host;
        const { width, height, widgetChildren: children } = parent;
        for (let i = 0, j = children.length; i < j; i++) {
            const widget = children[i];
            const lopt = (widget.layoutOptions || LayoutOptions.DEFAULT);
            let x = lopt.x ? lopt.x : 0;
            let y = lopt.y ? lopt.y : 0;
            if (Math.abs(x) < 1) {
                x *= width;
            }
            if (Math.abs(y) < 1) {
                y *= height;
            }
            const anchor = lopt.anchor || FastLayoutOptions.DEFAULT_ANCHOR;
            const l = x - (anchor.x * widget.getMeasuredWidth());
            const t = y - (anchor.y * widget.getMeasuredHeight());
            widget.layout(l, t, l + widget.getMeasuredWidth(), t + widget.getMeasuredHeight());
        }
    }
    getMeasuredWidth() {
        return this._measuredWidth;
    }
    getMeasuredHeight() {
        return this._measuredHeight;
    }
}

/**
 * A widget group is a layout owner that can position its children according
 * to the layout given to it.
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.Widget
 * @example
 * ```
 * const group = new PUXI.InteractiveGroup();
 *
 * group.useLayout(new PUXI.AnchorLayout());
 *
 * group.addChild(new PUXI.Button({ text: "Hey" })
 *  .setLayoutOptions(
 *      new PUXI.AnchorLayoutOptions({
 *             anchorLeft: 100,
 *             anchorTop: 300,
 *             anchorRight: .4,
 *             anchorBottom: 500,
 *             horizontalAlign: PUXI.ALIGN.CENTER
 *      })
 *  )
 * )
 * ```
 */
class WidgetGroup extends Widget {
    /**
     * Will set the given layout-manager to be used for positioning child widgets.
     *
     * @param {PUXI.ILayoutManager} layoutMgr
     */
    useLayout(layoutMgr) {
        if (this.layoutMgr) {
            this.layoutMgr.onDetach();
        }
        this.layoutMgr = layoutMgr;
        if (layoutMgr) {
            this.layoutMgr.onAttach(this);
        }
        return this;
    }
    /**
     * Sets the widget-recommended layout manager. By default (if not overriden by widget
     * group class), this is a fast-layout.
     */
    useDefaultLayout() {
        this.useLayout(new FastLayout());
    }
    onMeasure(width, height, widthMode, heightMode) {
        super.onMeasure(width, height, widthMode, heightMode);
        if (this.widgetChildren.length === 0) {
            return;
        }
        if (!this.layoutMgr) {
            this.useDefaultLayout();
        }
        this.layoutMgr.onMeasure(width, height, widthMode, heightMode);
        this._measuredWidth = Math.max(this.measuredWidth, this.layoutMgr.getMeasuredWidth());
        this._measuredHeight = Math.max(this.measuredHeight, this.layoutMgr.getMeasuredHeight());
    }
    onLayout(l, t, r, b, dirty = true) {
        super.onLayout(l, t, r, b, dirty);
        if (this.widgetChildren.length === 0) {
            return;
        }
        if (!this.layoutMgr) {
            this.useDefaultLayout();
        }
        this.layoutMgr.onLayout(); // layoutMgr is attached to this
    }
}

/**
 * An interactive container.
 *
 * @class
 * @extends PUXI.WidgetGroup
 * @memberof PUXI
 */
class InteractiveGroup extends WidgetGroup {
    constructor() {
        super();
        this.hitArea = new Rectangle();
        this.insetContainer.hitArea = this.hitArea;
    }
    update() {
        super.update();
    }
    layout(l, t, r, b, dirty) {
        super.layout(l, t, r, b, dirty);
        this.hitArea.width = this.width;
        this.hitArea.height = this.height;
    }
}

/**
 * Represents a view that can gain or loose focus. It is primarily subclassed by
 * input/form widgets.
 *
 * Generally, it is a good idea not use layouts on these types of widgets.
 *
 * @class
 * @extends PUXI.Widget
 * @memberof PUXI
 */
class FocusableWidget extends InteractiveGroup {
    /**
     * @param {PUXI.IInputBaseOptions} options
     * @param {PIXI.Container}[options.background]
     * @param {number}[options.tabIndex]
     * @param {any}[options.tabGroup]
     */
    constructor(options = {}) {
        super();
        this.bindEvents = () => {
            this.stage.on('pointerdown', this.onDocumentPointerDownImpl);
            document.addEventListener('keydown', this.onKeyDownImpl);
        };
        this.clearEvents = () => {
            this.stage.off('pointerdown', this.onDocumentPointerDownImpl);
            document.removeEventListener('keydown', this.onKeyDownImpl);
        };
        this.onKeyDownImpl = (e) => {
            const focusCtl = this.stage.focusController;
            if (e.which === 9 && focusCtl.useTab) {
                focusCtl.onTab();
                e.preventDefault();
            }
            else if (e.which === 38 && focusCtl.useBack) {
                focusCtl.onBack();
                e.preventDefault();
            }
            else if (e.which === 40 && focusCtl.useForward) {
                focusCtl.onForward();
                e.preventDefault();
            }
            this.emit('keydown');
        };
        this.onDocumentPointerDownImpl = () => {
            if (!this._isMousePressed) {
                this.blur();
            }
        };
        if (options.background) {
            super.setBackground(options.background);
        }
        // Prevents double focusing/blurring.
        this._isFocused = false;
        // Used to lose focus when mouse-down outside widget.
        this._isMousePressed = false;
        this.interactive = true;
        /**
         * @member {number}
         * @readonly
         */
        this.tabIndex = options.tabIndex;
        /**
         * The name of the tab group in which this widget's focus will move on
         * pressing tab.
         * @member {PUXI.TabGroup}
         * @readonly
         */
        this.tabGroup = options.tabGroup;
        this.insetContainer.on('pointerdown', () => {
            this.focus();
            this._isMousePressed = true;
        });
        this.insetContainer.on('pointerup', () => { this._isMousePressed = false; });
        this.insetContainer.on('pointerupoutside', () => { this._isMousePressed = false; });
    }
    /**
     * Brings this widget into focus.
     */
    focus() {
        if (this.isFocused) {
            return;
        }
        this.stage.focusController.notifyFocus(this);
        this._isFocused = true;
        this.bindEvents();
        this.emit('focusChanged', true);
        this.emit('focus');
    }
    /**
     * Brings this widget out of focus.
     */
    blur() {
        if (!this._isFocused) {
            return;
        }
        this.stage.focusController.notifyBlur();
        this._isFocused = false;
        this.clearEvents();
        this.emit('focusChanged', false);
        this.emit('blur');
    }
    /**
     * Whether this widget is in focus.
     * @member {boolean}
     * @readonly
     */
    get isFocused() {
        return this._isFocused;
    }
    initialize() {
        super.initialize();
        this.stage.focusController.in(this, this.tabIndex, this.tabGroup);
    }
}

/**
 * A static text widget. It cannot retain children.
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.Widget
 */
class TextWidget extends Widget {
    /**
     * @param {string} text - text content
     * @param {PIXI.TextStyle} textStyle - styled used for text
     */
    constructor(text, textStyle) {
        super();
        this.textDisplay = new Text(text, textStyle);
        this.contentContainer.addChild(this.textDisplay);
    }
    update() {
        if (this.tint !== null) {
            this.textDisplay.tint = this.tint;
        }
        if (this.blendMode !== null) {
            this.textDisplay.blendMode = this.blendMode;
        }
    }
    get value() {
        return this.textDisplay.text;
    }
    set value(val) {
        this.textDisplay.text = val;
    }
    get text() {
        return this.value;
    }
    set text(val) {
        this.value = val;
    }
}

/**
 * Button that can be clicked.
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.FocusableWidget
 */
class Button extends FocusableWidget {
    /**
     * @param [options.background}] {(PIXI.UI.SliceSprite|PIXI.UI.Sprite)} will be used as background for Button
     * @param [options.text=null] {PIXI.UI.Text} optional text
     * @param [options.tabIndex=0] {Number} input tab index
     * @param [options.tabGroup=0] {Number|String} input tab group
     * @param [options.width=options.background.width] {Number|String} width
     * @param [options.height=options.background.height] {Number|String} height
     */
    constructor(options) {
        super(options);
        this.isHover = false;
        if (typeof options.text === 'string') {
            options.text = new TextWidget(options.text, new TextStyle());
        }
        this.textWidget = options.text.setLayoutOptions(new FastLayoutOptions({
            width: LayoutOptions.WRAP_CONTENT,
            height: LayoutOptions.WRAP_CONTENT,
            x: 0.5,
            y: 0.5,
            anchor: FastLayoutOptions.CENTER_ANCHOR,
        }));
        if (this.textWidget) {
            this.addChild(this.textWidget);
        }
        this.contentContainer.buttonMode = true;
        this.setupClick();
    }
    setupClick() {
        const clickEvent = this.eventBroker.click;
        clickEvent.onHover = (e, over) => {
            this.isHover = over;
            this.emit('hover', over);
        };
        clickEvent.onPress = (e, isPressed) => {
            if (isPressed) {
                this.focus();
                e.data.originalEvent.preventDefault();
            }
            this.emit('press', isPressed);
        };
        clickEvent.onClick = (e) => {
            this.click();
        };
        this.click = () => {
            this.emit('click');
        };
    }
    update() {
        super.update();
        // No update needed
    }
    initialize() {
        super.initialize();
        this.setupClick();
        this.insetContainer.interactiveChildren = false;
        // lazy to make sure all children is initialized (trying to get the bedst hitArea possible)
    }
    /**
     * Label for this button.
     * @member {string}
     */
    get value() {
        if (this.textWidget) {
            return this.textWidget.text;
        }
        return '';
    }
    set value(val) {
        if (this.textWidget) {
            this.textWidget.text = val;
        }
    }
    get text() {
        return this.textWidget;
    }
    set text(val) {
        this.value = val;
    }
}
/*
 * Features:
 * Button, radio button (checkgroups)
 *
 * Methods:
 * blur()
 * focus()
 *
 * Properties:
 * checked: get/set Button checked
 * value: get/set Button value
 *
 * Events:
 * "hover"          param: [bool]isHover (hover/leave)
 * "press"          param: [bool]isPressed (pointerdown/pointerup)
 * "click"
 * "blur"
 * "focus"
 * "focusChanged"   param: [bool]isFocussed
 *
 */

/**
 * @memberof PUXI
 * @extends PUXI.IFocusableOptions
 * @member {boolean} checked
 * @member {PIXI.Container}[checkmark]
 * @member {PUXI.CheckGroup}[checkGroup]
 * @member {string}[value]
 */
/**
 * A checkbox is a button can be selected (checked). It has a on/off state that
 * can be controlled by the user.
 *
 * When used in a checkbox group, the group will control whether the checkbox can
 * be selected or not.
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.FocusableWidget
 */
class CheckBox extends FocusableWidget {
    /**
     * @param {PUXI.ICheckBoxOptions} options
     * @param [options.checked=false] {bool} is checked
     * @param options.background {(PIXI.UI.SliceSprite|PIXI.UI.Sprite)} will be used as background for CheckBox
     * @param options.checkmark {(PIXI.UI.SliceSprite|PIXI.UI.Sprite)} will be used as checkmark for CheckBox
     * @param {PUXI.CheckGroup}[options.checkGroup=null] CheckGroup name
     * @param options.value {String} mostly used along with checkgroup
     * @param [options.tabIndex=0] {Number} input tab index
     * @param [options.tabGroup=0] {Number|String} input tab group
     */
    constructor(options) {
        super(options);
        this.change = (val) => {
            if (this.checkmark) {
                this.checkmark.alpha = val ? 1 : 0;
            }
        };
        this.click = () => {
            this.emit('click');
            if (this.checkGroup !== null && this.checked) {
                return;
            }
            this.checked = !this.checked;
            this.emit('changed', this.checked);
        };
        this._checked = options.checked !== undefined ? options.checked : false;
        this._value = options.value || '';
        this.checkGroup = options.checkGroup || null;
        this.checkmark = new InteractiveGroup();
        this.checkmark.contentContainer.addChild(options.checkmark);
        this.checkmark.setLayoutOptions(new FastLayoutOptions({
            width: LayoutOptions.WRAP_CONTENT,
            height: LayoutOptions.WRAP_CONTENT,
            x: 0.5,
            y: 0.5,
            anchor: FastLayoutOptions.CENTER_ANCHOR,
        }));
        this.checkmark.alpha = this._checked ? 1 : 0;
        this.addChild(this.checkmark);
        this.contentContainer.buttonMode = true;
    }
    update() {
        // No need for updating
    }
    get checked() {
        return this._checked;
    }
    set checked(val) {
        if (val !== this._checked) {
            if (this.checkGroup !== null && val) {
                this.stage.checkBoxGroupController.notifyCheck(this);
            }
            this._checked = val;
            this.change(val);
        }
    }
    get value() {
        return this._value;
    }
    set value(val) {
        this._value = val;
        if (this.checked) {
            this.stage.checkBoxGroupController.notifyCheck(this);
        }
    }
    get selectedValue() {
        var _a;
        return (_a = this.stage) === null || _a === void 0 ? void 0 : _a.checkBoxGroupController.getSelected(this.checkGroup).value;
    }
    initialize() {
        super.initialize();
        const clickMgr = this.eventBroker.click;
        clickMgr.onHover = (_, over) => {
            this.emit('hover', over);
        };
        clickMgr.onPress = (e, isPressed) => {
            if (isPressed) {
                this.focus();
                e.data.originalEvent.preventDefault();
            }
            this.emit('press', isPressed);
        };
        clickMgr.onClick = () => {
            this.click();
        };
        if (this.checkGroup !== null) {
            this.stage.checkBoxGroupController.in(this, this.checkGroup);
        }
    }
}
/*
 * Features:
 * checkbox, radio button (checkgroups)
 *
 * Methods:
 * blur()
 * focus()
 * change(checked) //only exposed to overwrite (if you dont want to hard toggle alpha of checkmark)
 *
 * Properties:
 * checked: get/set checkbox checked
 * value: get/set checkbox value
 * selectedValue: get/set selected value for checkgroup
 *
 * Events:
 * "hover"          param: [bool]isHover (hover/leave)
 * "press"          param: [bool]isPressed (pointerdown/pointerup)
 * "click"
 * "blur"
 * "focus"
 * "focusChanged"   param: [bool]isFocussed
 * "change"         param: [bool]isChecked
 *
 */

class EaseBase
{
    getPosition(p)
    {
        return p;
    }
}

function ExponentialEase(power, easeIn, easeOut)
{
    const pow = power;
    const t = easeIn && easeOut ? 3 : easeOut ? 1 : 2;

    this.getPosition = function (p)
    {
        let r = (t === 1) ? 1 - p : (t === 2) ? p : (p < 0.5) ? p * 2 : (1 - p) * 2;

        if (pow === 1)
        {
            r *= r;
        }
        else if (pow === 2)
        {
            r *= r * r;
        }
        else if (pow === 3)
        {
            r *= r * r * r;
        }
        else if (pow === 4)
        {
            r *= r * r * r * r;
        }

        return (t === 1) ? 1 - r : (t === 2) ? r : (p < 0.5) ? r / 2 : 1 - (r / 2);
    };
}

ExponentialEase.prototype = Object.create(EaseBase.prototype);
ExponentialEase.prototype.constructor = ExponentialEase;

const Ease = {};

const HALF_PI = Math.PI * 0.5;

function create(fn)
{
    const e = Object.create(EaseBase.prototype);

    e.getPosition = fn;

    return e;
}

const Linear = new EaseBase();

// Liear
Ease.Linear = Linear;

// Exponetial Eases
function wrapEase(easeInFunction, easeOutFunction, easeInOutFunction)
{
    return {
        easeIn: easeInFunction,
        easeOut: easeOutFunction,
        easeInOut: easeInOutFunction,
    };
}

Ease.Power0 = {
    easeNone: Linear,
};

Ease.Power1 = Ease.Quad = wrapEase(
    new ExponentialEase(1, 1, 0),
    new ExponentialEase(1, 0, 1),
    new ExponentialEase(1, 1, 1));

Ease.Power2 = Ease.Cubic = wrapEase(
    new ExponentialEase(2, 1, 0),
    new ExponentialEase(2, 0, 1),
    new ExponentialEase(2, 1, 1));

Ease.Power3 = Ease.Quart = wrapEase(
    new ExponentialEase(3, 1, 0),
    new ExponentialEase(3, 0, 1),
    new ExponentialEase(3, 1, 1));

Ease.Power4 = Ease.Quint = wrapEase(
    new ExponentialEase(4, 1, 0),
    new ExponentialEase(4, 0, 1),
    new ExponentialEase(4, 1, 1));

// Bounce
Ease.Bounce = {
    BounceIn: create(function (p)
    {
        if ((p = 1 - p) < 1 / 2.75)
        {
            return 1 - (7.5625 * p * p);
        }
        else if (p < 2 / 2.75)
        {
            return 1 - (7.5625 * (p -= 1.5 / 2.75) * p + 0.75);
        }
        else if (p < 2.5 / 2.75)
        {
            return 1 - (7.5625 * (p -= 2.25 / 2.75) * p + 0.9375);
        }

        return 1 - (7.5625 * (p -= 2.625 / 2.75) * p + 0.984375);
    }),
    BounceOut: create(function (p)
    {
        if (p < 1 / 2.75)
        {
            return 7.5625 * p * p;
        }
        else if (p < 2 / 2.75)
        {
            return 7.5625 * (p -= 1.5 / 2.75) * p + 0.75;
        }
        else if (p < 2.5 / 2.75)
        {
            return 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375;
        }

        return 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375;
    }),
    BounceInOut: create(function (p)
    {
        const invert = (p < 0.5);

        if (invert)
        {
            p = 1 - (p * 2);
        }
        else
        {
            p = (p * 2) - 1;
        }
        if (p < 1 / 2.75)
        {
            p = 7.5625 * p * p;
        }
        else if (p < 2 / 2.75)
        {
            p = 7.5625 * (p -= 1.5 / 2.75) * p + 0.75;
        }
        else if (p < 2.5 / 2.75)
        {
            p = 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375;
        }
        else
        {
            p = 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375;
        }

        return invert ? (1 - p) * 0.5 : p * 0.5 + 0.5;
    }),
};

// Circ
Ease.Circ = {
    CircIn: create(function (p)
    {
        return -(Math.sqrt(1 - (p * p)) - 1);
    }),
    CircOut: create(function (p)
    {
        return Math.sqrt(1 - (p = p - 1) * p);
    }),
    CircInOut: create(function (p)
    {
        return ((p *= 2) < 1) ? -0.5 * (Math.sqrt(1 - p * p) - 1) : 0.5 * (Math.sqrt(1 - (p -= 2) * p) + 1);
    }),
};

// Expo
Ease.Expo = {
    ExpoIn: create(function (p)
    {
        return Math.pow(2, 10 * (p - 1)) - 0.001;
    }),
    ExpoOut: create(function (p)
    {
        return 1 - Math.pow(2, -10 * p);
    }),
    ExpoInOut: create(function (p)
    {
        return ((p *= 2) < 1) ? 0.5 * Math.pow(2, 10 * (p - 1)) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
    }),
};

// Sine
Ease.Sine = {
    SineIn: create(function (p)
    {
        return -Math.cos(p * HALF_PI) + 1;
    }),
    SineOut: create(function (p)
    {
        return Math.sin(p * HALF_PI);
    }),
    SineInOut: create(function (p)
    {
        return -0.5 * (Math.cos(Math.PI * p) - 1);
    }),
};

const Helpers = {
    Lerp(start, stop, amt) {
        if (amt > 1)
            amt = 1;
        else if (amt < 0)
            amt = 0;
        return start + (stop - start) * amt;
    },
    Round(number, decimals) {
        const pow = Math.pow(10, decimals);
        return Math.round(number * pow) / pow;
    },
    componentToHex(c) {
        const hex = c.toString(16);
        return hex.length == 1 ? `0${hex}` : hex;
    },
    rgbToHex(r, g, b) {
        return `#${this.componentToHex(r)}${this.componentToHex(g)}${this.componentToHex(b)}`;
    },
    rgbToNumber(r, g, b) {
        return r * 65536 + g * 256 + b;
    },
    numberToRgb(c) {
        return {
            r: Math.floor(c / (256 * 256)),
            g: Math.floor(c / 256) % 256,
            b: c % 256,
        };
    },
    hexToRgb(hex) {
        if (hex === null) {
            hex = 0xffffff;
        }
        if (!isNaN(hex)) {
            return this.numberToRgb(hex);
        }
        // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
        const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
        hex = hex.replace(shorthandRegex, function (m, r, g, b) {
            return r + r + g + g + b + b;
        });
        const result = (/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i).exec(hex);
        return result ? {
            r: parseInt(result[1], 16),
            g: parseInt(result[2], 16),
            b: parseInt(result[3], 16),
        } : null;
    },
};

/**
 * @memberof PUXI
 * @interface ISliderOptions
 * @property {PIXI.Container}[track]
 * @property {PIXI.Container}[handle]
 */
/**
 * These options are used to configure a `PUXI.Slider`.
 *
 * @memberof PUXI
 * @interface ISliderOptions
 * @property {PIXI.Container}[track]
 * @property {PIXI.Container}[fill]
 * @property {boolean}[vertical]
 * @property {number}[value]
 * @property {number}[minValue]
 * @property {number}[maxValue]
 * @property {number}[decimals]
 * @property {Function}[onValueChange]
 * @property {Function}[onValueChanging]
 */
/**
 * A slider is a form of input to set a variable to a value in a continuous
 * range. It cannot have its own children.
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.Widget
 */
class Slider extends Widget {
    /**
     * @param options {Object} Slider settings
     * @param options.track {(PIXI.UI.SliceSprite|PIXI.UI.Sprite)}  Any type of UIOBject, will be used for the slider track
     * @param options.handle {(PIXI.UI.SliceSprite|PIXI.UI.Sprite)} will be used as slider handle
     * @param [options.fill=null] {(PIXI.UI.SliceSprite|PIXI.UI.Sprite)} will be used for slider fill
     * @param [options.vertical=false] {boolean} Direction of the slider
     * @param [options.value=0] {number} value of the slider
     * @param [options.minValue=0] {number} minimum value
     * @param [options.maxValue=100] {number} max value
     * @param [options.decimals=0] {boolean} the decimal precision (use negative to round tens and hundreds)
     * @param [options.onValueChange=null] {callback} Callback when the value has changed
     * @param [options.onValueChanging=null] {callback} Callback while the value is changing
     */
    constructor(options) {
        super();
        /**
         * The value expressed as a percentage from min. to max. This will always
         * be between 0 and 1.
         *
         * The actual value is: `this.minValue + this.percentValue * (this.maxValue - this.minValue`).
         *
         * @member {number}
         */
        this.percentValue = 0;
        this._disabled = false;
        this.fill = options.fill || null;
        this.percentValue = this._minValue;
        this._minValue = options.minValue || 0;
        this._maxValue = options.maxValue || 100;
        this.decimals = options.decimals || 0;
        this.orientation = options.orientation || Slider.HORIZONTAL;
        this.onValueChange = options.onValueChange || null;
        this.onValueChanging = options.onValueChanging || null;
        this.value = options.value || 50;
        // set options
        this.track = Widget.fromContent(options.track
            || (this.orientation === Slider.HORIZONTAL
                ? Slider.DEFAULT_HORIZONTAL_TRACK.clone()
                : Slider.DEFAULT_VERTICAL_TRACK.clone()));
        this.handle = Widget.fromContent(options.handle || Slider.DEFAULT_HANDLE.clone());
        this.addChild(this.track, this.handle); // initialize(), update() usage
        this._localCursor = new Point();
        this.handle.contentContainer.buttonMode = true;
    }
    initialize() {
        super.initialize();
        let startValue = 0;
        let trackSize;
        const triggerValueChange = () => {
            this.emit('change', this.value);
            if (this._lastChange != this.value) {
                this._lastChange = this.value;
                if (typeof this.onValueChange === 'function') {
                    this.onValueChange(this.value);
                }
            }
        };
        const triggerValueChanging = () => {
            this.emit('changing', this.value);
            if (this._lastChanging != this.value) {
                this._lastChanging = this.value;
                if (typeof this.onValueChanging === 'function') {
                    this.onValueChanging(this.value);
                }
            }
        };
        const updatePositionToMouse = (mousePosition, soft) => {
            this.percentValue = this.getValueAtPhysicalPosition(mousePosition);
            this.layoutHandle();
            triggerValueChanging();
        };
        // Handles dragging
        const handleDrag = this.handle.eventBroker.dnd;
        handleDrag.onPress = (event) => {
            event.stopPropagation();
        };
        handleDrag.onDragStart = () => {
            startValue = this.percentValue;
            trackSize = this.orientation === Slider.HORIZONTAL
                ? this.track.width
                : this.track.height;
        };
        handleDrag.onDragMove = (event, offset) => {
            this.percentValue = Math.max(0, Math.min(1, startValue + ((this.orientation === Slider.HORIZONTAL ? offset.x : offset.y) / trackSize)));
            triggerValueChanging();
            this.layoutHandle();
        };
        handleDrag.onDragEnd = () => {
            triggerValueChange();
            this.layoutHandle();
        };
        // Bar pressing/dragging
        const trackDrag = this.track.eventBroker.dnd;
        trackDrag.onPress = (event, isPressed) => {
            if (isPressed) {
                updatePositionToMouse(event.data.global);
            }
            event.stopPropagation();
        };
        trackDrag.onDragMove = (event) => {
            updatePositionToMouse(event.data.global);
        };
        trackDrag.onDragEnd = () => {
            triggerValueChange();
        };
        this.layoutHandle();
    }
    get value() {
        return Helpers.Round(Helpers.Lerp(this._minValue, this._maxValue, this.percentValue), this.decimals);
    }
    set value(val) {
        if (val === this.value) {
            return;
        }
        if (isNaN(val)) {
            throw new Error('Cannot use NaN as a value');
        }
        this.percentValue = (Math.max(this._minValue, Math.min(this._maxValue, val)) - this._minValue) / (this._maxValue - this._minValue);
        if (typeof this.onValueChange === 'function') {
            this.onValueChange(this.value);
        }
        if (typeof this.onValueChanging === 'function') {
            this.onValueChanging(this.value);
        }
        if (this.handle && this.initialized) {
            this.layoutHandle();
        }
    }
    get minValue() {
        return this._minValue;
    }
    set minValue(val) {
        this._minValue = val;
        this.update();
    }
    get maxValue() {
        return this._maxValue;
    }
    set maxValue(val) {
        this._maxValue = val;
        this.update();
    }
    get disabled() {
        return this._disabled;
    }
    set disabled(val) {
        if (val !== this._disabled) {
            this._disabled = val;
            this.handle.contentContainer.buttonMode = !val;
            this.handle.contentContainer.interactive = !val;
            this.track.contentContainer.interactive = !val;
        }
    }
    /**
     * @protected
     * @returns the amount of the freedom that handle has in physical units, i.e. pixels. This
     *      is the width of the track minus the handle's size.
     */
    getPhysicalRange() {
        return this.orientation === Slider.HORIZONTAL
            ? this.contentWidth - this.handle.getMeasuredWidth()
            : this.contentHeight - this.handle.getMeasuredHeight();
    }
    /**
     * @protected
     * @param {PIXI.Point} cursor
     * @returns the value of the slider if the handle's center were (globally)
     *      positioned at the given point.
     */
    getValueAtPhysicalPosition(cursor) {
        // Transform position
        const localCursor = this.contentContainer.toLocal(cursor, null, this._localCursor, true);
        let offset;
        let range;
        if (this.orientation === Slider.HORIZONTAL) {
            const handleWidth = this.handle.getMeasuredWidth();
            offset = localCursor.x - this.paddingLeft - (handleWidth / 4);
            range = this.contentWidth - handleWidth;
        }
        else {
            const handleHeight = this.handle.getMeasuredHeight();
            offset = localCursor.y - this.paddingTop - (handleHeight / 4);
            range = this.contentHeight - handleHeight;
        }
        return offset / range;
    }
    /**
     * Re-positions the handle. This should be called after `_value` has been changed.
     */
    layoutHandle() {
        const handle = this.handle;
        const handleWidth = handle.getMeasuredWidth();
        const handleHeight = handle.getMeasuredHeight();
        let width = this.width - this.paddingHorizontal;
        let height = this.height - this.paddingVertical;
        let handleX;
        let handleY;
        if (this.orientation === Slider.HORIZONTAL) {
            width -= handleWidth;
            handleY = (height - handleHeight) / 2;
            handleX = (width * this.percentValue);
        }
        else {
            height -= handleHeight;
            handleX = (width - handleWidth) / 2;
            handleY = (height * this.percentValue);
        }
        handle.layout(handleX, handleY, handleX + handleWidth, handleY + handleHeight);
    }
    /**
     * Slider measures itself using the track's natural dimensions in its non-oriented
     * direction. The oriented direction will be the equal the range's size times
     * the track's resolution.
     *
     * @param width
     * @param height
     * @param widthMode
     * @param heightMode
     */
    onMeasure(width, height, widthMode, heightMode) {
        const naturalWidth = ((this.orientation === Slider.HORIZONTAL)
            ? this._maxValue - this._minValue
            : Math.max(this.handle.contentContainer.width, this.track.contentContainer.width))
            + this.paddingHorizontal;
        const naturalHeight = ((this.orientation === Slider.VERTICAL)
            ? this._maxValue - this._minValue
            : Math.max(this.handle.contentContainer.height, this.track.contentContainer.height))
            + this.paddingVertical;
        switch (widthMode) {
            case MeasureMode.EXACTLY:
                this._measuredWidth = width;
                break;
            case MeasureMode.UNBOUNDED:
                this._measuredWidth = naturalWidth;
                break;
            case MeasureMode.AT_MOST:
                this._measuredWidth = Math.min(width, naturalWidth);
                break;
        }
        switch (heightMode) {
            case MeasureMode.EXACTLY:
                this._measuredHeight = height;
                break;
            case MeasureMode.UNBOUNDED:
                this._measuredHeight = naturalHeight;
                break;
            case MeasureMode.AT_MOST:
                this._measuredHeight = Math.min(height, naturalHeight);
                break;
        }
    }
    /**
     * `Slider` lays the track to fill all of its width and height. The handle is aligned
     * in the middle in the non-oriented direction.
     *
     * @param l
     * @param t
     * @param r
     * @param b
     * @param dirty
     * @override
     */
    onLayout(l, t, r, b, dirty) {
        super.onLayout(l, t, r, b, dirty);
        const { handle, track } = this;
        track.layout(0, 0, this.width - this.paddingHorizontal, this.height - this.paddingVertical);
        // Layout doesn't scale the widget
        // TODO: Create a Track widget, this won't work for custom tracks that don't wanna
        // scale (and it looks ugly.)
        track.insetContainer.width = track.width;
        track.insetContainer.height = track.height;
        handle.measure(this.width, this.height, MeasureMode.AT_MOST, MeasureMode.AT_MOST);
        this.layoutHandle();
    }
}
/**
 * The default track for horizontally oriented sliders.
 * @static
 */
Slider.DEFAULT_HORIZONTAL_TRACK = new Graphics()
    .beginFill(0xffffff, 1)
    .drawRect(0, 0, 16, 16) // natural width & height = 16
    .endFill()
    .lineStyle(1, 0x000000, 0.7, 1, true) // draw line in middle
    .moveTo(1, 8)
    .lineTo(15, 8);
/**
 * The default track for vertically oriented sliders.
 * @static
 */
Slider.DEFAULT_VERTICAL_TRACK = new Graphics()
    .beginFill(0xffffff, 1)
    .drawRect(0, 0, 16, 16) // natural width & height = 16
    .endFill()
    .lineStyle(1, 0x000000, 0.7, 1, true) // draw line in middle
    .moveTo(8, 1)
    .lineTo(8, 15);
/**
 * @static
 */
Slider.DEFAULT_HANDLE = new Graphics()
    .beginFill(0x000000)
    .drawCircle(16, 16, 8)
    .endFill()
    .beginFill(0x000000, 0.5)
    .drawCircle(16, 16, 16)
    .endFill();
/**
 * Horizontal orientation
 * @static
 */
Slider.HORIZONTAL = 0xff5;
/**
 * Vertical orientation
 * @static
 */
Slider.VERTICAL = 0xffe;

const _tweenItemCache = [];
const _callbackItemCache = [];
const _tweenObjects = {};
const _activeTweenObjects = {};
let _currentId = 1;
class TweenObject {
    constructor(object) {
        this.object = object;
        this.tweens = {};
        this.active = false;
        this.onUpdate = null;
    }
}
class CallbackItem {
    constructor() {
        this._ready = false;
        this.obj = null;
        this.parent = null;
        this.key = '';
        this.time = 0;
        this.callback = null;
        this.currentTime = 0;
    }
    remove() {
        this._ready = true;
        delete this.parent.tweens[this.key];
        if (!Object.keys(this.parent.tweens).length) {
            this.parent.active = false;
            this.parent.onUpdate = null;
            delete _activeTweenObjects[this.obj._tweenObjectId];
        }
    }
    set(obj, callback, time) {
        this.obj = obj.object;
        if (!this.obj._currentCallbackID) {
            this.obj._currentCallbackID = 1;
        }
        else {
            this.obj._currentCallbackID++;
        }
        this.time = time;
        this.parent = obj;
        this.callback = callback;
        this._ready = false;
        this.key = `cb_${this.obj._currentCallbackID}`;
        this.currentTime = 0;
        if (!this.parent.active) {
            this.parent.active = true;
            _activeTweenObjects[this.obj._tweenObjectId] = this.parent;
        }
    }
    update(delta) {
        this.currentTime += delta;
        if (this.currentTime >= this.time) {
            this.remove();
            this.callback.call(this.parent);
        }
    }
}
class TweenItem {
    constructor() {
        this._ready = false;
        this.parent = null;
        this.obj = null;
        this.key = '';
        this.from = 0;
        this.to = 0;
        this.time = 0;
        this.ease = 0;
        this.currentTime = 0;
        this.t = 0;
        this.isColor = false;
    }
    remove() {
        this._ready = true;
        delete this.parent.tweens[this.key];
        if (!Object.keys(this.parent.tweens).length) {
            this.parent.active = false;
            delete _activeTweenObjects[this.obj._tweenObjectId];
        }
    }
    set(obj, key, from, to, time, ease) {
        this.isColor = isNaN(from) && from[0] === '#' || isNaN(to) && to[0] === '#';
        this.parent = obj;
        this.obj = obj.object;
        this.key = key;
        this.surfix = getSurfix(to);
        if (this.isColor) {
            this.to = Helpers.hexToRgb(to);
            this.from = Helpers.hexToRgb(from);
            this.currentColor = { r: this.from.r, g: this.from.g, b: this.from.b };
        }
        else {
            this.to = getToValue(to);
            this.from = getFromValue(from, to, this.obj, key);
        }
        this.time = time;
        this.currentTime = 0;
        this.ease = ease;
        this._ready = false;
        if (!this.parent.active) {
            this.parent.active = true;
            _activeTweenObjects[this.obj._tweenObjectId] = this.parent;
        }
    }
    update(delta) {
        this.currentTime += delta;
        this.t = Math.min(this.currentTime, this.time) / this.time;
        if (this.ease) {
            this.t = this.ease.getPosition(this.t);
        }
        if (this.isColor) {
            this.currentColor.r = Math.round(Helpers.Lerp(this.from.r, this.to.r, this.t));
            this.currentColor.g = Math.round(Helpers.Lerp(this.from.g, this.to.g, this.t));
            this.currentColor.b = Math.round(Helpers.Lerp(this.from.b, this.to.b, this.t));
            this.obj[this.key] = Helpers.rgbToNumber(this.currentColor.r, this.currentColor.g, this.currentColor.b);
        }
        else {
            const val = Helpers.Lerp(this.from, this.to, this.t);
            this.obj[this.key] = this.surfix ? val + this.surfix : val;
        }
        if (this.currentTime >= this.time) {
            this.remove();
        }
    }
}
const widthKeys = ['width', 'minWidth', 'maxWidth', 'anchorLeft', 'anchorRight', 'left', 'right', 'x'];
const heightKeys = ['height', 'minHeight', 'maxHeight', 'anchorTop', 'anchorBottom', 'top', 'bottom', 'y'];
function getFromValue(from, to, obj, key) {
    // both number
    if (!isNaN(from) && !isNaN(to)) {
        return from;
    }
    // both percentage
    if (isNaN(from) && isNaN(to) && from.indexOf('%') !== -1 && to.indexOf('%') !== -1) {
        return parseFloat(from.replace('%', ''));
    }
    // convert from to px
    if (isNaN(from) && !isNaN(to) && from.indexOf('%') !== -1) {
        if (widthKeys.indexOf(key) !== -1) {
            return obj.parent._width * (parseFloat(from.replace('%', '')) * 0.01);
        }
        else if (heightKeys.indexOf(key) !== -1) {
            return obj.parent._height * (parseFloat(from.replace('%', '')) * 0.01);
        }
        return 0;
    }
    // convert from to percentage
    if (!isNaN(from) && isNaN(to) && to.indexOf('%') !== -1) {
        if (widthKeys.indexOf(key) !== -1) {
            return from / obj.parent._width * 100;
        }
        else if (heightKeys.indexOf(key) !== -1) {
            return from / obj.parent._height * 100;
        }
        return 0;
    }
    return 0;
}
function getSurfix(to) {
    if (isNaN(to) && to.indexOf('%') !== -1) {
        return '%';
    }
}
function getToValue(to) {
    if (!isNaN(to)) {
        return to;
    }
    if (isNaN(to) && to.indexOf('%') !== -1) {
        return parseFloat(to.replace('%', ''));
    }
}
function getObject(obj) {
    if (!obj._tweenObjectId) {
        obj._tweenObjectId = _currentId;
        _currentId++;
    }
    let object = _tweenObjects[obj._tweenObjectId];
    if (!object) {
        object = _tweenObjects[obj._tweenObjectId] = new TweenObject(obj);
    }
    return object;
}
function getTweenItem() {
    for (let i = 0; i < _tweenItemCache.length; i++) {
        if (_tweenItemCache[i]._ready) {
            return _tweenItemCache[i];
        }
    }
    const tween = new TweenItem();
    _tweenItemCache.push(tween);
    return tween;
}
function getCallbackItem() {
    for (let i = 0; i < _callbackItemCache.length; i++) {
        if (_callbackItemCache[i]._ready) {
            return _callbackItemCache[i];
        }
    }
    const cb = new CallbackItem();
    _callbackItemCache.push(cb);
    return cb;
}
const Tween = {
    to(obj, time, params, ease) {
        const object = getObject(obj);
        let onUpdate = null;
        for (const key in params) {
            if (key === 'onComplete') {
                const cb = getCallbackItem();
                cb.set(object, params[key], time);
                object.tweens[cb.key] = cb;
                continue;
            }
            if (key === 'onUpdate') {
                onUpdate = params[key];
                continue;
            }
            if (time) {
                const match = params[key] === obj[key];
                if (typeof obj[key] === 'undefined')
                    continue;
                if (match) {
                    if (object.tweens[key])
                        object.tweens[key].remove();
                }
                else {
                    if (!object.tweens[key]) {
                        object.tweens[key] = getTweenItem();
                    }
                    object.tweens[key].set(object, key, obj[key], params[key], time, ease);
                }
            }
        }
        if (time) {
            object.onUpdate = onUpdate;
        }
        else
            this.set(obj, params);
    },
    from(obj, time, params, ease) {
        const object = getObject(obj);
        let onUpdate = null;
        for (const key in params) {
            if (key === 'onComplete') {
                const cb = getCallbackItem();
                cb.set(object, params[key], time);
                object.tweens[cb.key] = cb;
                continue;
            }
            if (key === 'onUpdate') {
                onUpdate = params[key];
                continue;
            }
            if (time) {
                const match = params[key] == obj[key];
                if (typeof obj[key] === 'undefined')
                    continue;
                if (match) {
                    if (object.tweens[key])
                        object.tweens[key].remove();
                }
                else {
                    if (!object.tweens[key]) {
                        object.tweens[key] = getTweenItem();
                    }
                    object.tweens[key].set(object, key, params[key], obj[key], time, ease);
                }
            }
        }
        if (time) {
            object.onUpdate = onUpdate;
        }
        else
            this.set(obj, params);
    },
    fromTo(obj, time, paramsFrom, paramsTo, ease) {
        const object = getObject(obj);
        let onUpdate = null;
        for (const key in paramsTo) {
            if (key === 'onComplete') {
                const cb = getCallbackItem();
                cb.set(object, paramsTo[key], time);
                object.tweens[cb.key] = cb;
                continue;
            }
            if (key === 'onUpdate') {
                onUpdate = paramsTo[key];
                continue;
            }
            if (time) {
                const match = paramsFrom[key] == paramsTo[key];
                if (typeof obj[key] === 'undefined' || typeof paramsFrom[key] === 'undefined')
                    continue;
                if (match) {
                    if (object.tweens[key])
                        object.tweens[key].remove();
                    obj[key] = paramsTo[key];
                }
                else {
                    if (!object.tweens[key]) {
                        object.tweens[key] = getTweenItem();
                    }
                    object.tweens[key].set(object, key, paramsFrom[key], paramsTo[key], time, ease);
                }
            }
        }
        if (time) {
            object.onUpdate = onUpdate;
        }
        else
            this.set(obj, paramsTo);
    },
    set(obj, params) {
        const object = getObject(obj);
        for (const key in params) {
            if (typeof obj[key] === 'undefined')
                continue;
            if (object.tweens[key])
                object.tweens[key].remove();
            obj[key] = params[key];
        }
    },
    _update(delta) {
        for (const id in _activeTweenObjects) {
            const object = _activeTweenObjects[id];
            for (const key in object.tweens) {
                object.tweens[key].update(delta);
            }
            if (object.onUpdate) {
                object.onUpdate.call(object.object, delta);
            }
        }
    },
};

/**
 * @memberof PUXI
 * @interface IScrollBarOptions
 * @property {PUXI.Sprite} track
 * @property {PUXI.Sprite} handle
 */
/**
 * An UI scrollbar to control a ScrollingContainer
 *
 * @class
 * @extends PUXI.Slider
 * @memberof PUXI
 * @param options {Object} ScrollBar settings
 * @param options.track {(PIXI.UI.SliceSprite|PIXI.UI.Sprite)}  Any type of UIOBject, will be used for the scrollbar track
 * @param options.handle {(PIXI.UI.SliceSprite|PIXI.UI.Sprite)} will be used as scrollbar handle
 * @param options.scrollingContainer {PIXI.UI.ScrollingContainer} The container to control
 * @param [options.vertical=false] {boolean} Direction of the scrollbar
 * @param [options.autohide=false] {boolean} Hides the scrollbar when not needed
 */
class ScrollBar extends Slider {
    constructor(options) {
        super({
            track: options.track || ScrollBar.DEFAULT_TRACK.clone(),
            handle: options.handle || ScrollBar.DEFAULT_HANDLE.clone(),
            fill: null,
            orientation: options.orientation,
            minValue: 0,
            maxValue: 1,
        });
        this.scrollingContainer = options.scrollingContainer;
        this.autohide = options.autohide;
        this._hidden = false;
    }
    initialize() {
        super.initialize();
        this.decimals = 3; // up decimals to trigger ValueChanging more often
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        this.on('changing', () => {
            this.scrollingContainer.forcePctPosition(this.orientation === Slider.HORIZONTAL ? 'x' : 'y', this.percentValue);
        });
        this.on('change', () => {
            this.scrollingContainer.setScrollPosition();
        });
    }
    toggleHidden(hidden) {
        if (this.autohide) {
            if (hidden && !this._hidden) {
                Tween.to(this, 0.2, { alpha: 0 });
                this._hidden = true;
            }
            else if (!hidden && this._hidden) {
                Tween.to(this, 0.2, { alpha: 1 });
                this._hidden = false;
            }
        }
    }
}
/**
 * @static
 */
ScrollBar.DEFAULT_TRACK = new Graphics()
    .beginFill(0xffffff)
    .drawRect(0, 0, 8, 8)
    .endFill();
/**
 * @static
 */
ScrollBar.DEFAULT_HANDLE = new Graphics()
    .beginFill(0x000000)
    .drawCircle(8, 8, 4)
    .endFill()
    .beginFill(0x000000, 0.5)
    .drawCircle(8, 8, 8)
    .endFill();

/**
 * `AnchorLayout` is used in conjunction with `AnchorLayoutOptions`.
 *
 * @memberof PUXI
 * @class
 * @example
 * ```
 * parent.useLayout(new PUXI.AnchorLayout());
 * ```
 */
class AnchorLayout {
    onAttach(host) {
        this.host = host;
    }
    onDetach() {
        this.host = null;
    }
    onLayout() {
        const parent = this.host;
        const { widgetChildren } = parent;
        const parentWidth = parent.getMeasuredWidth();
        const parentHeight = parent.getMeasuredHeight();
        for (let i = 0; i < widgetChildren.length; i++) {
            const child = widgetChildren[i];
            const layoutOptions = (child.layoutOptions || {});
            let childWidth = child.getMeasuredWidth();
            let childHeight = child.getMeasuredHeight();
            const anchorLeft = this.calculateAnchor(layoutOptions.anchorLeft || 0, parentWidth, false);
            const anchorTop = this.calculateAnchor(layoutOptions.anchorTop || 0, parentHeight, false);
            const anchorRight = this.calculateAnchor(layoutOptions.anchorRight || 0, parentWidth, true);
            const anchorBottom = this.calculateAnchor(layoutOptions.anchorBottom || 0, parentHeight, true);
            let x = 0;
            let y = 0;
            if (childWidth !== 0) {
                switch (layoutOptions.horizontalAlign || ALIGN.NONE) {
                    case ALIGN.LEFT:
                        x = anchorLeft;
                        break;
                    case ALIGN.MIDDLE:
                        x = (anchorRight - anchorLeft - childWidth) / 2;
                        break;
                    case ALIGN.RIGHT:
                        x = anchorRight - childWidth;
                        break;
                }
            }
            else {
                x = anchorLeft;
                childWidth = anchorRight - anchorLeft;
            }
            if (childHeight !== 0) {
                switch (layoutOptions.verticalAlign || ALIGN.NONE) {
                    case ALIGN.TOP:
                        y = anchorTop;
                        break;
                    case ALIGN.MIDDLE:
                        y = (anchorBottom - anchorTop - childHeight) / 2;
                        break;
                    case ALIGN.RIGHT:
                        y = anchorBottom - childWidth;
                        break;
                }
            }
            else {
                y = anchorRight;
                childHeight = anchorBottom - anchorTop;
            }
            child.layout(x, y, x + childWidth, y + childHeight);
        }
    }
    onMeasure(widthLimit, heightLimit, widthMode, heightMode) {
        const children = this.host.widgetChildren;
        let naturalWidth = 0;
        let naturalHeight = 0;
        const baseWidthMode = widthMode === MeasureMode.EXACTLY ? MeasureMode.AT_MOST : widthMode;
        const baseHeightMode = heightMode === MeasureMode.EXACTLY ? MeasureMode.AT_MOST : heightMode;
        let hasFillers = false;
        // First pass: measure children and find our natural width/height. If we have a upper
        // limit, then pass that on.
        for (let i = 0, j = children.length; i < j; i++) {
            const widget = children[i];
            const lopt = (widget.layoutOptions || LayoutOptions.DEFAULT);
            const anchorLeft = this.calculateAnchor(lopt.anchorLeft || 0, widthLimit, false);
            const anchorTop = this.calculateAnchor(lopt.anchorTop || 0, heightLimit, false);
            const anchorRight = this.calculateAnchor(lopt.anchorRight || 0, widthLimit, true);
            const anchorBottom = this.calculateAnchor(lopt.anchorBottom || 0, heightLimit, true);
            // Does child have a pre-determined width or height?
            const widthFill = lopt.width === LayoutOptions.FILL_PARENT && widthMode === MeasureMode.EXACTLY;
            const heightFill = lopt.height === LayoutOptions.FILL_PARENT && heightMode === MeasureMode.EXACTLY;
            // Fillers need to be remeasured using EXACTLY.
            hasFillers = hasFillers || lopt.width === LayoutOptions.FILL_PARENT || lopt.height === LayoutOptions.FILL_PARENT;
            widget.measure(anchorRight - anchorLeft, anchorBottom - anchorTop, widthFill ? MeasureMode.EXACTLY : baseWidthMode, heightFill ? MeasureMode.EXACTLY : baseHeightMode);
            const horizontalReach = this.calculateReach(lopt.anchorLeft || 0, lopt.anchorRight || 0, widget.getMeasuredWidth());
            const verticalReach = this.calculateReach(lopt.anchorTop || 0, lopt.anchorBottom || 0, widget.getMeasuredHeight());
            naturalWidth = Math.max(naturalWidth, horizontalReach);
            naturalHeight = Math.max(naturalHeight, verticalReach);
        }
        this.measuredWidth = Widget.resolveMeasuredDimen(naturalWidth, widthLimit, widthMode);
        this.measuredHeight = Widget.resolveMeasuredDimen(naturalHeight, heightLimit, heightMode);
        if (!hasFillers) {
            return;
        }
        // Handle fillers.
        for (let i = 0, j = children.length; i < j; i++) {
            const widget = children[i];
            const lopt = (widget.layoutOptions || LayoutOptions.DEFAULT);
            const anchorLeft = this.calculateAnchor(lopt.anchorLeft || 0, this.measuredWidth, false);
            const anchorTop = this.calculateAnchor(lopt.anchorTop || 0, this.measuredHeight, false);
            const anchorRight = this.calculateAnchor(lopt.anchorRight || 0, this.measuredWidth, true);
            const anchorBottom = this.calculateAnchor(lopt.anchorBottom || 0, this.measuredHeight, true);
            if (lopt.width === LayoutOptions.FILL_PARENT || lopt.height === LayoutOptions.FILL_PARENT) {
                widget.measure(anchorRight - anchorLeft, anchorBottom - anchorTop, lopt.width === LayoutOptions.FILL_PARENT ? MeasureMode.EXACTLY : MeasureMode.AT_MOST, lopt.height === LayoutOptions.FILL_PARENT ? MeasureMode.EXACTLY : MeasureMode.AT_MOST);
            }
        }
    }
    getMeasuredWidth() {
        return this.measuredWidth;
    }
    getMeasuredHeight() {
        return this.measuredHeight;
    }
    /**
     * Calculates the actual value of the anchor, given the parent's dimension.
     *
     * @param {number} anchor - anchor as given in layout options
     * @param {number} limit - parent's dimension
     * @param {boolean} limitSubtract - true of right/bottom anchors, false for left/top
     */
    calculateAnchor(anchor, limit, limitSubtract) {
        const offset = Math.abs(anchor) < 1 ? anchor * limit : anchor;
        return limitSubtract ? limit - offset : offset;
    }
    /**
     * Calculates the "reach" of a child widget, which is the minimum dimension of
     * the parent required to fully fit the child.
     *
     * @param {number} startAnchor - left or top anchor as given in layout options
     * @param {number} endAnchor - right or bottom anchor as given in layout options
     * @param {number} dimen - measured dimension of the widget (width or height)
     */
    calculateReach(startAnchor, endAnchor, dimen) {
        if (Math.abs(startAnchor) < 1 && Math.abs(endAnchor) < 1) {
            return dimen / (1 - endAnchor - startAnchor);
        }
        else if (Math.abs(startAnchor) < 1) {
            return (endAnchor + dimen) / (1 - startAnchor);
        }
        else if (Math.abs(endAnchor) < 1) {
            return (startAnchor + dimen) / (1 - endAnchor);
        }
        return startAnchor + dimen + endAnchor;
    }
}

const { REGION_LEFT, REGION_TOP, REGION_RIGHT, REGION_BOTTOM, REGION_CENTER, } = BorderLayoutOptions;
const { FILL_PARENT, } = LayoutOptions;
const { EXACTLY, AT_MOST, } = MeasureMode;
/**
 * `PUXI.BorderLayout` is used in conjunction with `PUXI.BorderLayoutOptions`.
 *
 * This layout guarantees that the "center" region will always be in the center of
 * the widget-group.
 *
 * WARNING: This layout may have some bugs in edge cases that haven't been reported.
 *
 * @memberof PUXI
 * @class
 * @implements PUXI.ILayoutManager
 */
class BorderLayout {
    constructor() {
        this.leftWidgets = [];
        this.topWidgets = [];
        this.rightWidgets = [];
        this.bottomWidgets = [];
        this.centerWidgets = [];
    }
    onAttach(host) {
        this.host = host;
    }
    onDetach() {
        this.host = null;
        this.clearMeasureCache();
        this.clearRegions();
    }
    onLayout() {
        this.layoutChildren(this.leftWidgets, 0, this.measuredTopHeight, this.measuredLeftWidth, this.measuredCenterHeight);
        this.layoutChildren(this.topWidgets, 0, 0, this.measuredWidth, this.measuredTopHeight);
        this.layoutChildren(this.rightWidgets, this.measuredWidth - this.measuredRightWidth, this.measuredTopHeight, this.measuredRightWidth, this.measuredCenterHeight);
        this.layoutChildren(this.bottomWidgets, 0, this.measuredTopHeight + this.measuredCenterHeight, this.measuredWidth, this.measuredBottomHeight);
        this.layoutChildren(this.centerWidgets, this.measuredLeftWidth, this.measuredTopHeight, this.measuredCenterWidth, this.measuredCenterHeight);
    }
    layoutChildren(widgets, regionX, regionY, regionWidth, regionHeight) {
        var _a, _b;
        for (let i = 0, j = widgets.length; i < j; i++) {
            const widget = widgets[i];
            let x = 0;
            let y = 0;
            switch ((_a = widget.layoutOptions) === null || _a === void 0 ? void 0 : _a.horizontalAlign) {
                case ALIGN.CENTER:
                    x = (regionWidth - widget.getMeasuredWidth()) / 2;
                    break;
                case ALIGN.RIGHT:
                    x = regionWidth - widget.getMeasuredWidth();
                    break;
                default:
                    x = 0;
                    break;
            }
            switch ((_b = widget.layoutOptions) === null || _b === void 0 ? void 0 : _b.verticalAlign) {
                case ALIGN.CENTER:
                    y = (regionHeight - widget.getMeasuredHeight()) / 2;
                    break;
                case ALIGN.BOTTOM:
                    y = regionHeight - widget.getMeasuredHeight();
                    break;
                default:
                    y = 0;
                    break;
            }
            x += regionX;
            y += regionY;
            widget.layout(x, y, x + widget.getMeasuredWidth(), y + widget.getMeasuredHeight(), true);
        }
    }
    /**
     * @param {number} maxWidth
     * @param {number} maxHeight
     * @param {PUXI.MeasureMode} widthMode
     * @param {PUXI.MeasureMode} heightMode
     * @override
     */
    onMeasure(maxWidth, maxHeight, widthMode, heightMode) {
        this.indexRegions();
        this.clearMeasureCache();
        // Children can be aligned inside region if smaller
        const childWidthMode = widthMode === MeasureMode.EXACTLY ? MeasureMode.AT_MOST : widthMode;
        const childHeightMode = heightMode === MeasureMode.EXACTLY ? MeasureMode.AT_MOST : widthMode;
        // Measure top, bottom, and center. The max. of each row's width will be our "reference".
        let [tw, th, , thmin] = this.measureChildren(// eslint-disable-line prefer-const
        this.topWidgets, maxWidth, maxHeight, childWidthMode, childHeightMode);
        let [bw, bh, , bhmin] = this.measureChildren(// eslint-disable-line prefer-const
        this.bottomWidgets, maxWidth, maxHeight, childWidthMode, childHeightMode);
        let [cw, ch, cwmin, chmin] = this.measureChildren(// eslint-disable-line prefer-const
        this.centerWidgets, maxWidth, maxHeight, childWidthMode, heightMode);
        // Measure left & right regions. Their heights will equal center's height.
        let [lw, , lwmin] = this.measureChildren(// eslint-disable-line prefer-const
        this.leftWidgets, maxWidth, ch, childWidthMode, MeasureMode.AT_MOST);
        let [rw, , rwmin] = this.measureChildren(// eslint-disable-line prefer-const
        this.rightWidgets, maxWidth, ch, childWidthMode, MeasureMode.AT_MOST);
        // Check if total width/height is greater than our limit. If so, then downscale
        // each row's height or each column's (in middle row) width.
        const middleRowWidth = lw + rw + cw;
        const netWidth = Math.max(tw, bw, middleRowWidth);
        const netHeight = th + bh + ch;
        // Resolve our limits.
        if (widthMode === MeasureMode.EXACTLY) {
            this.measuredWidth = maxWidth;
        }
        else if (widthMode === MeasureMode.AT_MOST) {
            this.measuredWidth = Math.min(netWidth, maxWidth);
        }
        else {
            this.measuredWidth = netWidth;
        }
        if (heightMode === MeasureMode.EXACTLY) {
            this.measuredHeight = maxHeight;
        }
        else if (heightMode === MeasureMode.AT_MOST) {
            this.measuredHeight = Math.min(netHeight, maxHeight);
        }
        else {
            this.measuredHeight = netHeight;
        }
        tw = this.measuredWidth;
        bw = this.measuredWidth;
        if (netHeight > this.measuredHeight) {
            const hmin = (thmin + chmin + bhmin);
            // Redistribute heights minus min-heights.
            if (hmin < this.measuredHeight) {
                const downscale = (this.measuredHeight - hmin) / (netHeight - hmin);
                th = thmin + ((th - thmin) * downscale);
                bh = bhmin + ((bh - bhmin) * downscale);
                ch = chmin + ((ch - chmin) * downscale);
            }
            // Redistribute full heights.
            else {
                const downscale = this.measuredHeight / netHeight;
                th *= downscale;
                bh *= downscale;
                ch *= downscale;
            }
        }
        if (netWidth > this.measuredWidth) {
            const wmin = lwmin + cwmin + rwmin;
            // Redistribute widths minus min. widths
            if (wmin < this.measuredWidth) {
                const downscale = (this.measuredWidth - wmin) / (netWidth - wmin);
                lw = lwmin + ((lw - lwmin) * downscale);
                cw = cwmin + ((cw - cwmin) * downscale);
                rw = rwmin + ((rw - rwmin) * downscale);
            }
            // Redistribute full widths
            else {
                const downscale = this.measuredWidth / netWidth;
                lw *= downscale;
                cw *= downscale;
                rw *= downscale;
            }
        }
        // Useful to know!
        this.measuredLeftWidth = lw;
        this.measuredRightWidth = rw;
        this.measuredCenterWidth = cw;
        this.measuredTopHeight = th;
        this.measuredBottomHeight = bh;
        this.measuredCenterHeight = ch;
        this.fitChildren(this.leftWidgets, this.measuredLeftWidth, this.measuredCenterHeight);
        this.fitChildren(this.topWidgets, this.measuredWidth, this.measuredTopHeight);
        this.fitChildren(this.rightWidgets, this.measuredRightWidth, this.measuredCenterHeight);
        this.fitChildren(this.bottomWidgets, this.measuredWidth, this.measuredBottomHeight);
        this.fitChildren(this.centerWidgets, this.measuredCenterWidth, this.measuredCenterHeight);
    }
    /**
     * This measures the list of widgets given the constraints. The max width and
     * height amongst the children is returned.
     *
     * @param {PUXI.Widget[]} list
     * @param {number} maxWidth
     * @param {number} maxHeight
     * @param {PUXI.MeasureMode} widthMode
     * @param {PUXI.MeasureMode} heightMode
     * @returns {number[]} - [width, height, widthFixedLowerBound, heightFixedLowerBound] -
     *    the max. width and height amongst children. Also, the minimum required width/height
     *    for the region (as defined in layout-options).
     */
    measureChildren(list, maxWidth, maxHeight, widthMode, heightMode) {
        let width = 0;
        let height = 0;
        let widthFixedLowerBound = 0;
        let heightFixedLowerBound = 0;
        for (let i = 0, j = list.length; i < j; i++) {
            const widget = list[i];
            const lopt = widget.layoutOptions || LayoutOptions.DEFAULT;
            let w = maxWidth;
            let h = maxHeight;
            let wmd = widthMode;
            let hmd = heightMode;
            if (lopt.width <= LayoutOptions.MAX_DIMEN) {
                w = lopt.width;
                wmd = EXACTLY;
                widthFixedLowerBound = Math.max(widthFixedLowerBound, w);
            }
            if (lopt.height <= LayoutOptions.MAX_DIMEN) {
                h = lopt.height;
                hmd = EXACTLY;
                heightFixedLowerBound = Math.max(heightFixedLowerBound, h);
            }
            widget.measure(w, h, wmd, hmd);
            width = Math.max(width, widget.getMeasuredWidth());
            height = Math.max(height, widget.getMeasuredHeight());
        }
        return [width, height, widthFixedLowerBound, heightFixedLowerBound];
    }
    /**
     * Ensures all widgets in the list measured their dimensions below the region
     * width & height. Widgets that are too large are remeasured in the those
     * limits (using `MeasureMode.AT_MOST`).
     *
     * This will handle widgets that have "FILL_PARENT" width or height.
     *
     * @param {PUXI.Widget[]} list
     * @param {number} measuredRegionWidth
     * @param {number} measuredRegionHeight
     */
    fitChildren(list, measuredRegionWidth, measuredRegionHeight) {
        var _a, _b, _c, _d;
        for (let i = 0, j = list.length; i < j; i++) {
            const widget = list[i];
            if (widget.getMeasuredWidth() <= measuredRegionWidth
                && widget.getMeasuredHeight() <= measuredRegionHeight
                && widget.getMeasuredWidth() > 0
                && widget.getMeasuredHeight() > 0
                && ((_a = widget.layoutOptions) === null || _a === void 0 ? void 0 : _a.width) !== FILL_PARENT
                && ((_b = widget.layoutOptions) === null || _b === void 0 ? void 0 : _b.height) !== FILL_PARENT) {
                continue;
            }
            if (measuredRegionWidth > 0 && measuredRegionHeight > 0) {
                const wm = ((_c = widget.layoutOptions) === null || _c === void 0 ? void 0 : _c.width) === FILL_PARENT ? EXACTLY : AT_MOST;
                const hm = ((_d = widget.layoutOptions) === null || _d === void 0 ? void 0 : _d.height) === FILL_PARENT ? EXACTLY : AT_MOST;
                widget.measure(measuredRegionWidth, measuredRegionHeight, wm, hm);
            }
        }
    }
    /**
     * Indexes the list of left, top, right, bottom, and center widget lists.
     */
    indexRegions() {
        this.clearRegions();
        const { widgetChildren: children } = this.host;
        for (let i = 0, j = children.length; i < j; i++) {
            const widget = children[i];
            const lopt = (widget.layoutOptions || LayoutOptions.DEFAULT);
            const region = lopt.region || REGION_CENTER;
            switch (region) {
                case REGION_LEFT:
                    this.leftWidgets.push(widget);
                    break;
                case REGION_TOP:
                    this.topWidgets.push(widget);
                    break;
                case REGION_RIGHT:
                    this.rightWidgets.push(widget);
                    break;
                case REGION_BOTTOM:
                    this.bottomWidgets.push(widget);
                    break;
                default: this.centerWidgets.push(widget);
            }
        }
    }
    /**
     * Clears the left, top, right, bottom, and center widget lists.
     */
    clearRegions() {
        this.leftWidgets.length = 0;
        this.topWidgets.length = 0;
        this.rightWidgets.length = 0;
        this.bottomWidgets.length = 0;
    }
    /**
     * Zeros the measured dimensions.
     */
    clearMeasureCache() {
        this.measuredLeftWidth = 0;
        this.measuredRightWidth = 0;
        this.measuredCenterWidth = 0;
        this.measuredTopHeight = 0;
        this.measuredBottomHeight = 0;
        this.measuredCenterHeight = 0;
    }
    getMeasuredWidth() {
        return this.measuredWidth;
    }
    getMeasuredHeight() {
        return this.measuredHeight;
    }
}

/**
 * `ScrollWidget` masks its contents to its layout bounds and translates
 * its children when scrolling. It uses the anchor layout.
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.InteractiveGroup
 */
class ScrollWidget extends InteractiveGroup {
    /**
     * @param {PUXI.IScrollingContainerOptions} options
     * @param [options.scrollX=false] {Boolean} Enable horizontal scrolling
     * @param [options.scrollY=false] {Boolean} Enable vertical scrolling
     * @param [options.dragScrolling=true] {Boolean} Enable mousedrag scrolling
     * @param [options.softness=0.5] {Number} (0-1) softness of scrolling
     * @param [options.width=0] {Number|String} container width
     * @param [options.height=0] {Number} container height
     * @param [options.radius=0] {Number} corner radius of clipping mask
     * @param [options.expandMask=0] {Number} mask expand (px)
     * @param [options.overflowY=0] {Number} how much can be scrolled past content dimensions
     * @param [options.overflowX=0] {Number} how much can be scrolled past content dimensions
     */
    constructor(options = {}) {
        super();
        this.forcePctPosition = (direction, pct) => {
            const bounds = this.getInnerBounds();
            const container = this.innerContainer.insetContainer;
            if (this.scrollX && direction === 'x') {
                container.position[direction] = -((bounds.width - this.width) * pct);
            }
            if (this.scrollY && direction === 'y') {
                container.position[direction] = -((bounds.height - this.height) * pct);
            }
            this.scrollPosition[direction] = this.targetPosition[direction] = container.position[direction];
        };
        this.focusPosition = (pos) => {
            const bounds = this.getInnerBounds();
            const container = this.innerContainer.insetContainer;
            let dif;
            if (this.scrollX) {
                const x = Math.max(0, (Math.min(bounds.width, pos.x)));
                if (x + container.x > this.width) {
                    dif = x - this.width;
                    container.x = -dif;
                }
                else if (x + container.x < 0) {
                    dif = x + container.x;
                    container.x -= dif;
                }
            }
            if (this.scrollY) {
                const y = Math.max(0, (Math.min(bounds.height, pos.y)));
                if (y + container.y > this.height) {
                    dif = y - this.height;
                    container.y = -dif;
                }
                else if (y + container.y < 0) {
                    dif = y + container.y;
                    container.y -= dif;
                }
            }
            this.lastPosition.copyFrom(container.position);
            this.targetPosition.copyFrom(container.position);
            this.scrollPosition.copyFrom(container.position);
            this.updateScrollBars();
        };
        /**
         * @param {PIXI.Point}[velocity]
         */
        this.setScrollPosition = (velocity) => {
            if (velocity) {
                this.scrollVelocity.copyFrom(velocity);
            }
            const container = this.innerContainer.insetContainer;
            if (!this.animating) {
                this.animating = true;
                this.lastPosition.copyFrom(container.position);
                this.targetPosition.copyFrom(container.position);
                Ticker$1.shared.add(this.updateScrollPosition);
            }
        };
        /**
         * @param {number} delta
         * @protected
         */
        this.updateScrollPosition = (delta) => {
            this.stop = true;
            if (this.scrollX) {
                this.updateDirection('x', delta);
            }
            if (this.scrollY) {
                this.updateDirection('y', delta);
            }
            if (this.stop) {
                Ticker$1.shared.remove(this.updateScrollPosition);
                this.animating = false;
            }
            this.updateScrollBars();
        };
        /**
         * @param {'x' | 'y'} direction
         * @param {number} delta
         * @protected
         */
        this.updateDirection = (direction, delta) => {
            const bounds = this.getInnerBounds();
            const { scrollPosition, scrollVelocity, targetPosition, lastPosition, } = this;
            const container = this.innerContainer.insetContainer;
            let min;
            if (direction === 'y') {
                min = Math.round(Math.min(0, this.height - bounds.height));
            }
            else {
                min = Math.round(Math.min(0, this.width - bounds.width));
            }
            if (!this.scrolling && Math.round(scrollVelocity[direction]) !== 0) {
                targetPosition[direction] += scrollVelocity[direction];
                scrollVelocity[direction] = Helpers.Lerp(scrollVelocity[direction], 0, (5 + 2.5 / Math.max(this.softness, 0.01)) * delta);
                if (targetPosition[direction] > 0) {
                    targetPosition[direction] = 0;
                }
                else if (targetPosition[direction] < min) {
                    targetPosition[direction] = min;
                }
            }
            if (!this.scrolling
                && Math.round(scrollVelocity[direction]) === 0
                && (container[direction] > 0
                    || container[direction] < min)) {
                const target = this.scrollPosition[direction] > 0 ? 0 : min;
                scrollPosition[direction] = Helpers.Lerp(scrollPosition[direction], target, (40 - (30 * this.softness)) * delta);
                this.stop = false;
            }
            else if (this.scrolling || Math.round(scrollVelocity[direction]) !== 0) {
                if (this.scrolling) {
                    scrollVelocity[direction] = this.scrollPosition[direction] - lastPosition[direction];
                    lastPosition.copyFrom(scrollPosition);
                }
                if (targetPosition[direction] > 0) {
                    scrollVelocity[direction] = 0;
                    scrollPosition[direction] = 100 * this.softness * (1 - Math.exp(targetPosition[direction] / -200));
                }
                else if (targetPosition[direction] < min) {
                    scrollVelocity[direction] = 0;
                    scrollPosition[direction] = min - (100 * this.softness * (1 - Math.exp((min - targetPosition[direction]) / -200)));
                }
                else {
                    scrollPosition[direction] = targetPosition[direction];
                }
                this.stop = false;
            }
            container.position[direction] = Math.round(scrollPosition[direction]);
        };
        this.mask = new Graphics();
        this.innerContainer = new InteractiveGroup();
        this.innerBounds = new Rectangle();
        super.addChild(this.innerContainer);
        this.contentContainer.addChild(this.mask);
        this.contentContainer.mask = this.mask;
        this.scrollX = options.scrollX !== undefined ? options.scrollX : false;
        this.scrollY = options.scrollY !== undefined ? options.scrollY : false;
        this.dragScrolling = options.dragScrolling !== undefined ? options.dragScrolling : true;
        this.softness = options.softness !== undefined ? Math.max(Math.min(options.softness || 0, 1), 0) : 0.5;
        this.radius = options.radius || 0;
        this.expandMask = options.expandMask || 0;
        this.overflowY = options.overflowY || 0;
        this.overflowX = options.overflowX || 0;
        this.scrollVelocity = new Point();
        /**
         * Widget's position in a scroll.
         * @member {PIXI.Point}
         * @private
         */
        this.scrollPosition = new Point();
        /**
         * Position that the cursor is at, i.e. our scroll "target".
         * @member {PIXI.Point}
         * @private
         */
        this.targetPosition = new Point();
        this.lastPosition = new Point();
        this.useLayout(new BorderLayout());
        this.animating = false;
        this.scrolling = false;
        this.scrollBars = [];
        if (options.scrollBars && this.scrollX) {
            const horizontalScrollBar = new ScrollBar({
                orientation: ScrollBar.HORIZONTAL,
                scrollingContainer: this,
                minValue: 0,
                maxValue: 1,
            })
                .setLayoutOptions(new BorderLayoutOptions({
                width: LayoutOptions.FILL_PARENT,
                height: LayoutOptions.WRAP_CONTENT,
                region: BorderLayoutOptions.REGION_BOTTOM,
                horizontalAlign: ALIGN.CENTER,
                verticalAlign: ALIGN.BOTTOM,
            }))
                .setBackground(0xff)
                .setBackgroundAlpha(0.8);
            super.addChild(horizontalScrollBar);
            this.scrollBars.push(horizontalScrollBar);
        }
        if (options.scrollBars && this.scrollY) {
            const verticalScrollBar = new ScrollBar({
                orientation: ScrollBar.VERTICAL,
                scrollingContainer: this,
                minValue: 0,
                maxValue: 1,
            })
                .setLayoutOptions(new BorderLayoutOptions({
                width: LayoutOptions.WRAP_CONTENT,
                height: LayoutOptions.FILL_PARENT,
                region: BorderLayoutOptions.REGION_RIGHT,
                horizontalAlign: ALIGN.RIGHT,
                verticalAlign: ALIGN.CENTER,
            }))
                .setBackground(0xff)
                .setBackgroundAlpha(0.8);
            super.addChild(verticalScrollBar);
            this.scrollBars.push(verticalScrollBar);
        }
        this.boundCached = 0;
    }
    /**
     * Updates the mask and scroll position before rendering.
     *
     * @override
     */
    update() {
        super.update();
        if (this.lastWidth !== this.width || this.lastHeight !== this.height) {
            const of = this.expandMask;
            this.mask.clear();
            this.mask.lineStyle(0);
            this.mask.beginFill(0xFFFFFF, 1);
            if (this.radius === 0) {
                this.mask.drawRect(-of, -of, this.width + of, this.height + of);
            }
            else {
                this.mask.drawRoundedRect(-of, -of, this.width + of, this.height + of, this.radius);
            }
            this.mask.endFill();
            this.lastWidth = this.width;
            this.lastHeight = this.height;
        }
    }
    /**
     * Adds this scrollbar. It is expected that the given scrollbar has been
     * given proper border-layout options.
     *
     * @todo This only works for TOP, LEFT scrollbars as BOTTOM, RIGHT are occupied.
     * @param {PUXI.ScrollBar} scrollBar
     */
    addScrollBar(scrollBar) {
        super.addChild(scrollBar);
        this.scrollBars.push(scrollBar);
        return this;
    }
    /**
     * @param {PUXI.Widget[]} newChildren
     * @returns {ScrollWidget} this widget
     */
    addChild(...newChildren) {
        for (let i = 0; i < newChildren.length; i++) {
            this.innerContainer.addChild(newChildren[i]);
        }
        this.getInnerBounds(true); // make sure bounds is updated instantly when a child is added
        return this;
    }
    /**
     * Updates the scroll bar values, and should be called when scrolled.
     */
    updateScrollBars() {
        for (let i = 0, j = this.scrollBars.length; i < j; i++) {
            const scrollBar = this.scrollBars[i];
            if (scrollBar.orientation === Slider.HORIZONTAL) {
                const x = this.getPercentPosition('x');
                scrollBar.value = x;
            }
            else if (scrollBar.orientation === Slider.VERTICAL) {
                const y = this.getPercentPosition('y');
                scrollBar.value = y;
            }
        }
    }
    getInnerBounds(force) {
        // this is a temporary fix, because we cant rely on innercontainer height if the children is positioned > 0 y.
        if (force || performance.now() - this.boundCached > 1000) {
            this.innerContainer.insetContainer.getLocalBounds(this.innerBounds);
            this.innerBounds.height = this.innerBounds.y + this.innerContainer.height || 0;
            this.innerBounds.width = this.innerBounds.x + this.innerContainer.width || 0;
            this.boundCached = performance.now();
        }
        return this.innerBounds;
    }
    /**
     * @override
     */
    initialize() {
        super.initialize();
        if (this.scrollX || this.scrollY) {
            this.initScrolling();
        }
    }
    initScrolling() {
        const container = this.innerContainer.insetContainer;
        const realPosition = new Point();
        const { scrollPosition, targetPosition, } = this;
        // Drag scroll
        if (this.dragScrolling) {
            const drag = this.eventBroker.dnd;
            drag.onDragStart = (e) => {
                if (!this.scrolling) {
                    realPosition.copyFrom(container.position);
                    scrollPosition.copyFrom(container.position);
                    this.scrolling = true;
                    this.setScrollPosition();
                    this.emit('scrollstart', e);
                }
            };
            drag.onDragMove = (_, offset) => {
                if (this.scrollX) {
                    targetPosition.x = realPosition.x + offset.x;
                }
                if (this.scrollY) {
                    targetPosition.y = realPosition.y + offset.y;
                }
                this.setScrollPosition();
            };
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            drag.onDragEnd = (e) => {
                if (this.scrolling) {
                    this.scrolling = false;
                    this.emit('scrollend', e);
                }
            };
        }
        // Mouse scroll
        const scrollSpeed = new Point();
        const scroll = new ScrollManager(this, true);
        scroll.onMouseScroll = (e, delta) => {
            scrollSpeed.set(-delta.x * 0.2, -delta.y * 0.2);
            this.setScrollPosition(scrollSpeed);
        };
        this.updateScrollBars();
    }
    /**
     * @param {string} direction - `'x'` or `'y'`
     * @returns {number} a value between 0 and 1 indicating how scrolling
     *      has occured in that direction (called percent position).
     */
    getPercentPosition(direction) {
        const bounds = this.getInnerBounds();
        const container = this.innerContainer.insetContainer;
        if (direction === 'x') {
            return container.x / (this.width - bounds.width);
        }
        else if (direction === 'y') {
            return container.y / (this.height - bounds.height);
        }
        return 0;
    }
}

/**
 * An UI Container object
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.Widget
 * @param desc {Boolean} Sort the list descending
 * @param tweenTime {Number} if above 0 the sort will be animated
 * @param tweenEase {PIXI.UI.Ease} ease method used for animation
 */
class SortableList extends InteractiveGroup {
    constructor(desc, tweenTime, tweenEase) {
        super(0, 0);
        this.desc = typeof desc !== 'undefined' ? desc : false;
        this.tweenTime = tweenTime || 0;
        this.tweenEase = tweenEase;
        this.items = [];
    }
    addChild(UIObject, fnValue, fnThenBy) {
        super.addChild(UIObject);
        if (this.items.indexOf(UIObject) === -1) {
            this.items.push(UIObject);
        }
        if (typeof fnValue === 'function') {
            UIObject._sortListValue = fnValue;
        }
        if (typeof fnThenBy === 'function') {
            UIObject._sortListThenByValue = fnThenBy;
        }
        if (!UIObject._sortListRnd) {
            UIObject._sortListRnd = Math.random();
        }
        this.sort();
    }
    removeChild(UIObject) {
        if (arguments.length > 1) {
            for (let i = 0; i < arguments.length; i++) {
                this.removeChild(arguments[i]);
            }
        }
        else {
            super.removeChild(UIObject);
            const index = this.items.indexOf(UIObject);
            if (index !== -1) {
                this.items.splice(index, 1);
            }
            this.sort();
        }
    }
    sort(instant = false) {
        clearTimeout(this._sortTimeout);
        if (instant) {
            this._sort();
            return;
        }
        this._sortTimeout = setTimeout(() => { this._sort(); }, 0);
    }
    _sort() {
        const desc = this.desc;
        let y = 0;
        let alt = true;
        this.items.sort(function (a, b) {
            let res = a._sortListValue() < b._sortListValue() ? desc ? 1 : -1
                : a._sortListValue() > b._sortListValue() ? desc ? -1 : 1 : 0;
            if (res === 0 && a._sortListThenByValue && b._sortListThenByValue) {
                res = a._sortListThenByValue() < b._sortListThenByValue() ? desc ? 1 : -1
                    : a._sortListThenByValue() > b._sortListThenByValue() ? desc ? -1 : 1 : 0;
            }
            if (res === 0) {
                res = a._sortListRnd > b._sortListRnd ? 1
                    : a._sortListRnd < b._sortListRnd ? -1 : 0;
            }
            return res;
        });
        for (let i = 0; i < this.items.length; i++) {
            const item = this.items[i];
            alt = !alt;
            if (this.tweenTime > 0) {
                Tween.fromTo(item, this.tweenTime, { x: item.x, y: item.y }, { x: 0, y }, this.tweenEase);
            }
            else {
                item.x = 0;
                item.y = y;
            }
            y += item.height;
            if (typeof item.altering === 'function') {
                item.altering(alt);
            }
        }
        // force it to update parents when sort animation is done (prevent scrolling container bug)
        if (this.tweenTime > 0) {
            setTimeout(() => {
                this.updatesettings(false, true);
            }, this.tweenTime * 1000);
        }
    }
}

/**
 * A sliced sprite with dynamic width and height.
 *
 * @class
 * @memberof PUXI
 * @param Texture {PIXI.Texture} the texture for this SliceSprite
 * @param BorderWidth {Number} Width of the sprite borders
 * @param horizontalSlice {Boolean} Slice the sprite horizontically
 * @param verticalSlice {Boolean} Slice the sprite vertically
 * @param [tile=false] {Boolean} tile or streach
 */
class SliceSprite extends Widget {
    constructor(texture, borderWidth, horizontalSlice, verticalSlice, tile) {
        super(texture.width, texture.height);
        this.bw = borderWidth || 5;
        this.vs = typeof verticalSlice !== 'undefined' ? verticalSlice : true;
        this.hs = typeof horizontalSlice !== 'undefined' ? horizontalSlice : true;
        this.t = texture.baseTexture;
        this.f = texture.frame;
        this.tile = tile;
        if (this.hs) {
            this.setting.minWidth = borderWidth * 2;
        }
        if (this.vs) {
            this.setting.minHeight = borderWidth * 2;
        }
        /**
     * Updates the sliced sprites position and size
     *
     * @private
     */
        this.update = function () {
            if (!this.initialized)
                return;
            if (vs && hs) {
                str.x = sbr.x = sr.x = this._width - bw;
                sbl.y = sbr.y = sb.y = this._height - bw;
                sf.width = st.width = sb.width = this._width - bw * 2;
                sf.height = sl.height = sr.height = this._height - bw * 2;
            }
            else if (hs) {
                sr.x = this._width - bw;
                sl.height = sr.height = sf.height = this._height;
                sf.width = this._width - bw * 2;
            }
            else { // vs
                sb.y = this._height - bw;
                st.width = sb.width = sf.width = this._width;
                sf.height = this._height - bw * 2;
            }
            if (this.tint !== null) {
                sf.tint = this.tint;
                if (vs && hs)
                    stl.tint = str.tint = sbl.tint = sbr.tint = this.tint;
                if (hs)
                    sl.tint = sr.tint = this.tint;
                if (vs)
                    st.tint = sb.tint = this.tint;
            }
            if (this.blendMode !== null) {
                sf.blendMode = this.blendMode;
                if (vs && hs)
                    stl.blendMode = str.blendMode = sbl.blendMode = sbr.blendMode = this.blendMode;
                if (hs)
                    sl.blendMode = sr.blendMode = this.blendMode;
                if (vs)
                    st.blendMode = sb.blendMode = this.blendMode;
            }
        };
    }
    initialize() {
        super.initialize();
        const { f, bw } = this;
        // get frames
        if (this.vs && this.hs) {
            this.ftl = new Rectangle(f.x, f.y, bw, bw);
            this.ftr = new Rectangle(f.x + f.width - bw, f.y, bw, bw);
            this.fbl = new Rectangle(f.x, f.y + f.height - bw, bw, bw);
            this.fbr = new Rectangle(f.x + f.width - bw, f.y + f.height - bw, bw, bw);
            this.ft = new Rectangle(f.x + bw, f.y, f.width - bw * 2, bw);
            this.fb = new Rectangle(f.x + bw, f.y + f.height - bw, f.width - bw * 2, bw);
            this.fl = new Rectangle(f.x, f.y + bw, bw, f.height - bw * 2);
            this.fr = new Rectangle(f.x + f.width - bw, f.y + bw, bw, f.height - bw * 2);
            this.ff = new Rectangle(f.x + bw, f.y + bw, f.width - bw * 2, f.height - bw * 2);
        }
        else if (this.hs) {
            this.fl = new Rectangle(this.f.x, f.y, bw, f.height);
            this.fr = new Rectangle(f.x + f.width - bw, f.y, bw, f.height);
            this.ff = new Rectangle(f.x + bw, f.y, f.width - bw * 2, f.height);
        }
        else { // vs
            this.ft = new Rectangle(f.x, f.y, f.width, bw);
            this.fb = new Rectangle(f.x, f.y + f.height - bw, f.width, bw);
            this.ff = new Rectangle(f.x, f.y + bw, f.width, f.height - bw * 2);
        }
        // TODO: swap frames if rotation
        const { t, ff, fl, fr, ft, fb } = this;
        // make sprites
        this.sf = this.tile
            ? new extras.TilingSprite(new Texture(t, ff))
            : new Sprite$1(new Texture(t, ff));
        this.contentContainer.addChildAt(this.sf, 0);
        if (this.vs && this.hs) {
            this.stl = new Sprite$1(new Texture(t, this.ftl));
            this.str = new Sprite$1(new Texture(t, this.ftr));
            this.sbl = new Sprite$1(new Texture(t, this.fbl));
            this.sbr = new Sprite$1(new Texture(t, this.fbr));
            this.contentContainer.addChildAt(this.stl, 0);
            this.contentContainer.addChildAt(this.str, 0);
            this.contentContainer.addChildAt(this.sbl, 0);
            this.contentContainer.addChildAt(this.sbr, 0);
        }
        if (hs) {
            this.sl = this.tile
                ? new extras.TilingSprite(new Texture(t, fl))
                : new Sprite$1(new Texture(t, fl));
            this.sr = this.tile
                ? new extras.TilingSprite(new Texture(t, fr))
                : new Sprite$1(new Texture(t, fr));
            this.contentContainer.addChildAt(this.sl, 0);
            this.contentContainer.addChildAt(this.sr, 0);
        }
        if (this.vs) {
            this.st = this.tile
                ? new extras.TilingSprite(new Texture(t, ft))
                : new Sprite$1(new Texture(t, ft));
            this.sb = this.tile
                ? new extras.TilingSprite(new Texture(t, fb))
                : new Sprite$1(new Texture(t, fb));
            this.contentContainer.addChildAt(this.st, 0);
            this.contentContainer.addChildAt(this.sb, 0);
        }
        // set constant position and sizes
        if (this.vs && this.hs) {
            this.st.x = bw;
            this.sb.x = bw;
            this.sl.y = bw;
            this.sr.y = bw;
            this.stl.width = bw;
            this.str.width = bw;
            this.sbl.width = bw;
            this.sbr.width = bw;
            this.stl.height = bw;
            this.str.height = bw;
            this.sbl.height = bw;
            this.sbr.height = bw;
        }
        if (this.hs) {
            this.sf.x = this.sl.width = this.sr.width = bw;
        }
        if (this.vs) {
            this.sf.y = this.st.height = this.sb.height = bw;
        }
    }
    update() {
        // NO updates
    }
}

/**
 * An UI sprite object
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.Widget
 */
class Sprite extends Widget {
    constructor(texture) {
        super();
        this.spriteDisplay = new Sprite$1(texture);
        this.contentContainer.addChild(this.spriteDisplay);
    }
    update() {
        if (this.tint !== null) {
            this.spriteDisplay.tint = this.tint;
        }
        if (this.blendMode !== null) {
            this.spriteDisplay.blendMode = this.blendMode;
        }
    }
    static fromImage(imageUrl) {
        return new Sprite(new Texture(new BaseTexture(imageUrl)));
    }
}

class Controller extends utils.EventEmitter {
    constructor(stage) {
        super();
        this.stage = stage;
    }
}
/**
 * A controller handles a stage-level state that can be held by wigets. For example,
 * `PUXI.FocusController` handles which widget is focused.
 *
 * @memberof PUXI
 * @class Controller
 */
/**
 * Enables the widget to enter the controller's state.
 *
 * @memberof PUXI.Controller#
 * @method in
 * @param {PUXI.Widget} widget
 */
/**
 * Disables the widget from the controller's state.
 *
 * @memberof PUXI.Controller#
 * @method out
 * @param {PUXI.Widget} widget
 */

/**
 * Check boxes use this controller to deselect other checkboxes in the group when
 * they are selected.
 *
 * @memberof PUXI
 * @class
 * @extends PUXI.Controller
 */
class CheckBoxGroupController extends Controller {
    constructor(stage) {
        super(stage);
        this.checkGroups = new Map();
    }
    /**
     * @param {PUXI.CheckBox} widget
     * @param {PUXI.CheckGroup} checkGroup
     * @override
     */
    in(widget, checkGroup) {
        if (!checkGroup) {
            throw new Error('Default check groups don\'t exist!');
        }
        const group = this.checkGroups.get(checkGroup) || this.initGroup(checkGroup);
        group.checks.push(widget);
        widget.checkGroup = checkGroup;
    }
    /**
     * @override
     */
    out(widget) {
        const group = this.checkGroups.get(widget.checkGroup);
        const i = group.checks.indexOf(widget);
        if (i > 0) {
            group.checks.splice(i, 1);
        }
        widget.checkGroup = null;
    }
    /**
     * Called when a checkbox is selected. Do not call from outside.
     *
     * @param {CheckBox} widget
     */
    notifyCheck(widget) {
        const group = this.checkGroups.get(widget.checkGroup);
        if (!group) {
            return;
        }
        const { checks } = group;
        for (let i = 0, j = checks.length; i < j; i++) {
            if (checks[i] !== widget) {
                checks[i].checked = false;
            }
        }
        group.selected = widget;
    }
    /**
     * @param {PUXI.CheckGroup} group
     * @returns {CheckBox} the selected checkbox in the group
     */
    getSelected(group) {
        var _a;
        return (_a = this.checkGroups.get(group)) === null || _a === void 0 ? void 0 : _a.selected;
    }
    /**
     * Ensures that the check group exists in `this.checkGroups`.
     *
     * @param {PUXI.CheckGroup} id
     * @protected
     */
    initGroup(id) {
        const cgroup = {
            checks: [],
            selected: null,
        };
        this.checkGroups.set(id, cgroup);
        return cgroup;
    }
}

/**
 * Pressing tab on a focused widget will make the next widget its tab group
 * focused. If no tab group is specified for a focusable widget, then it
 * has the `'default'` tab group.
 *
 * @memberof PUXI
 * @typedef {string} TabGroup
 */
/**
 * @memberof PUXI
 * @class
 * @extends PUXI.Controller
 */
class FocusController extends Controller {
    constructor(stage) {
        super(stage);
        /**
         * Map of tab-group names to the widgets in those groups.
         * @member {Map<PUXI.TabGroup, PUXI.FocusableWidget[]>}
         * @protected
         */
        this.tabGroups = new Map();
        /**
         * Whether to enable tab-based focus movement.
         * @member {boolean}
         */
        this.useTab = true;
        /**
         * Whether to enable forward arrow key focus movement.
         * @member {boolean}
         */
        this.useForward = true;
        /**
         * Whether to enable back arrow key focus movement.
         * @member {boolean}
         */
        this.useBack = true;
    }
    /**
     * Adds the (focusable) widget to the tab group so that pressing tab repeatedly
     * will eventually bring it into focus.
     *
     * @param {PUXI.FocusableWidget} widget - the widget to add
     * @param {number}[tabIndex=0] - unique index for the widget in tab group used for ordering
     * @param {PUXI.TabGroup}[tabGroup='default'] - tab group name
     */
    in(widget, tabIndex = 0, tabGroup = 'default') {
        let widgets = this.tabGroups.get(tabGroup);
        if (!widgets) {
            widgets = [];
            this.tabGroups.set(tabGroup, widgets);
        }
        const i = widgets.indexOf(widget);
        // Push widget into tab group list if not present already.
        if (i === -1) {
            widget.tabIndex = tabIndex !== undefined ? tabIndex : -1;
            widget.tabGroup = tabGroup;
            widgets.push(widget);
            widgets.sort((a, b) => a.tabIndex - b.tabIndex);
        }
    }
    /**
     * @param {PUXI.FocusableWidget} widget
     * @override
     */
    out(widget) {
        const widgets = this.tabGroups.get(widget.tabGroup);
        if (!widgets) {
            return;
        }
        const i = widgets.indexOf(widget);
        if (i !== -1) {
            // Widgets should already be sorted & so deleting should not unsort it.
            widgets.splice(i, 1);
        }
    }
    /**
     * Called when a widget comes into focus. Do not call this yourself.
     *
     * @param {FocusableWidget} widget
     */
    notifyFocus(widget) {
        const lastItem = this.currentItem;
        if (lastItem) {
            lastItem.blur();
            this.emit('blur', lastItem);
        }
        this.currentItem = widget;
        this.emit('focus', widget);
        this.emit('focusChanged', widget, lastItem);
    }
    /**
     * Clears the currently focused item without blurring it. It is called
     * when a widget goes out of focus.
     */
    notifyBlur() {
        this.emit('blur', this.currentItem);
        this.emit('focusChanged', null, this.currentItem);
        this.currentItem = null;
    }
    /**
     * Brings the widget into focus.
     *
     * @param {FocusableWidget} item
     */
    focus(item) {
        const lastItem = this.currentItem;
        if (lastItem) {
            lastItem.blur();
            this.emit('blur', lastItem);
        }
        item.focus();
        this.emit('focus', item);
        this.emit('focusChanged', item, lastItem);
    }
    /**
     * Blurs the currently focused widget out of focus.
     */
    blur() {
        if (this.currentItem) {
            this.currentItem.blur();
            this.emit('blur', this.currentItem);
            this.emit('focusChanged', null, this.currentItem);
            this.currentItem = null;
        }
    }
    /**
     * Called when tab is pressed on a focusable widget.
     */
    onTab() {
        const { tabGroups, currentItem } = this;
        if (currentItem) {
            const tabGroup = tabGroups.get(currentItem.tabGroup);
            let i = tabGroup.indexOf(currentItem) + 1;
            if (i >= tabGroup.length) {
                i = 0;
            }
            this.focus(tabGroup[i]);
        }
    }
    /**
     * Focuses the next item without wrapping, i.e. it does not go to the first
     * item if the current one is the last item. This is called when the user
     * presses the forward arrow key.
     */
    onForward() {
        if (!this.useForward) {
            return;
        }
        const { currentItem, tabGroups } = this;
        if (currentItem) {
            const tabGroup = tabGroups.get(currentItem.tabGroup);
            let i = tabGroup.indexOf(currentItem) + 1;
            if (i >= tabGroup.length) {
                i = tabGroup.length - 1;
            }
            this.focus(tabGroup[i]);
        }
    }
    /**
     * Focuses the last item without wrapping, i.e. it does not go to the last
     * item if the current item is the first one. This is called when the user
     * presses the back arrow button.
     */
    onBack() {
        const { currentItem, tabGroups } = this;
        if (currentItem) {
            const tabGroup = tabGroups.get(currentItem.tabGroup);
            let i = tabGroup.indexOf(currentItem) - 1;
            if (i < 0)
                i = 0;
            this.focus(tabGroup[i]);
        }
    }
}

/**
 * The stage is the root node in the PUXI scene graph. It does not provide a
 * sophisticated layout model; however, it will accept constraints defined by
 * `PUXI.FastLayoutOptions` or `PUXI.LayoutOptions` in its children.
 *
 * The stage is not a `PUXI.Widget` and its dimensions are always fixed.
 *
 * @memberof PUXI
 * @class
 * @extends PIXI.Container
 */
class Stage extends Container {
    /**
     * @param {number} width - width of the stage
     * @param {number} height - height of the stage
     */
    constructor(width, height) {
        super();
        this.__width = width;
        this.__height = height;
        this.minWidth = 0;
        this.minHeight = 0;
        this.widgetChildren = [];
        this.interactive = true;
        this.stage = this;
        this.hitArea = new Rectangle(0, 0, 0, 0);
        this.initialized = true;
        this.resize(width, height);
        this._checkBoxGroupCtl = new Stage.CHECK_BOX_GROUP_CONTROLLER(this);
        this._focusCtl = new Stage.FOCUS_CONTROLLER(this);
    }
    measureAndLayout() {
        if (this.background) {
            this.background.width = this.width;
            this.background.height = this.height;
        }
        for (let i = 0, j = this.widgetChildren.length; i < j; i++) {
            const widget = this.widgetChildren[i];
            const lopt = (widget.layoutOptions || LayoutOptions.DEFAULT);
            const widthMeasureMode = lopt.width < LayoutOptions.MAX_DIMEN
                ? MeasureMode.EXACTLY
                : MeasureMode.AT_MOST;
            const heightMeasureMode = lopt.height < LayoutOptions.MAX_DIMEN
                ? MeasureMode.EXACTLY
                : MeasureMode.AT_MOST;
            const loptWidth = (Math.abs(lopt.width) < 1) ? lopt.width * this.width : lopt.width;
            const loptHeight = (Math.abs(lopt.height) < 1) ? lopt.height * this.height : lopt.height;
            widget.measure(widthMeasureMode === MeasureMode.EXACTLY ? loptWidth : this.width, heightMeasureMode === MeasureMode.EXACTLY ? loptHeight : this.height, widthMeasureMode, heightMeasureMode);
            let x = lopt.x ? lopt.x : 0;
            let y = lopt.y ? lopt.y : 0;
            if (Math.abs(x) < 1) {
                x *= this.width;
            }
            if (Math.abs(y) < 1) {
                y *= this.height;
            }
            const anchor = lopt.anchor || FastLayoutOptions.DEFAULT_ANCHOR;
            const l = x - (anchor.x * widget.getMeasuredWidth());
            const t = y - (anchor.y * widget.getMeasuredHeight());
            widget.layout(l, t, l + widget.getMeasuredWidth(), t + widget.getMeasuredHeight(), true);
        }
    }
    getBackground() {
        return this.background;
    }
    setBackground(bg) {
        if (this.background) {
            super.removeChild(this.background);
        }
        this.background = bg;
        if (bg) {
            super.addChildAt(bg, 0);
            this.background.width = this.width;
            this.background.height = this.height;
        }
    }
    update(widgets) {
        this.emit('preupdate', this);
        for (let i = 0, j = widgets.length; i < j; i++) {
            const widget = widgets[i];
            widget.stage = this;
            if (!widget.initialized) {
                widget.initialize();
            }
            this.update(widget.widgetChildren);
            widget.update();
        }
        this.emit('postupdate', this);
    }
    render(renderer) {
        this.update(this.widgetChildren);
        super.render(renderer);
    }
    addChild(UIObject) {
        const argumentLenght = arguments.length;
        if (argumentLenght > 1) {
            for (let i = 0; i < argumentLenght; i++) {
                this.addChild(arguments[i]);
            }
        }
        else {
            if (UIObject.parent) {
                UIObject.parent.removeChild(UIObject);
            }
            UIObject.parent = this;
            this.widgetChildren.push(UIObject);
            super.addChild(UIObject.insetContainer);
            // UIObject.updatesettings(true);
        }
        this.measureAndLayout();
    }
    removeChild(UIObject) {
        const argumentLenght = arguments.length;
        if (argumentLenght > 1) {
            for (let i = 0; i < argumentLenght; i++) {
                this.removeChild(arguments[i]);
            }
        }
        else {
            super.removeChild(UIObject.insetContainer);
            const index = this.widgetChildren.indexOf(UIObject);
            if (index !== -1) {
                this.children.splice(index, 1);
                UIObject.parent = null;
            }
        }
        this.measureAndLayout();
    }
    resize(width, height) {
        if (!isNaN(height))
            this.__height = height;
        if (!isNaN(width))
            this.__width = width;
        if (this.minWidth || this.minHeight) {
            let rx = 1;
            let ry = 1;
            if (width && width < this.minWidth) {
                rx = this.minWidth / width;
            }
            if (height && height < this.minHeight) {
                ry = this.minHeight / height;
            }
            if (rx > ry && rx > 1) {
                this.scale.set(1 / rx);
                this.__height *= rx;
                this.__width *= rx;
            }
            else if (ry > 1) {
                this.scale.set(1 / ry);
                this.__width *= ry;
                this.__height *= ry;
            }
            else if (this.scale.x !== 1) {
                this.scale.set(1);
            }
        }
        if (this.hitArea) {
            this.hitArea.width = this.__width;
            this.hitArea.height = this.__height;
        }
        for (let i = 0; i < this.widgetChildren.length; i++) {
            this.widgetChildren[i].updatesettings(true, false);
        }
        this.measureAndLayout();
    }
    get width() {
        return this.__width;
    }
    set width(val) {
        if (!isNaN(val)) {
            this.__width = val;
            this.resize();
        }
    }
    get height() {
        return this.__height;
    }
    set height(val) {
        if (!isNaN(val)) {
            this.__height = val;
            this.resize();
        }
    }
    /**
     * The check box group controller for check boxes in this stage.
     *
     * @member {PUXI.CheckBoxGroupController}
     */
    get checkBoxGroupController() {
        return this._checkBoxGroupCtl;
    }
    /**
     * The focus controller for widgets in this stage. You can use this to bring a
     * widget into focus.
     *
     * @member {PUXI.FocusController}
     */
    get focusController() {
        return this._focusCtl;
    }
}
/**
 * Use this to override which class is used for the check box group controller. It
 * should extend from `PUXI.CheckBoxGroupController`.
 *
 * @static
 */
Stage.CHECK_BOX_GROUP_CONTROLLER = CheckBoxGroupController;
/**
 * Use this to override which class is used for the focus controller. It should
 * extend from `PUXI.FocusController`.
 *
 * @static
 */
Stage.FOCUS_CONTROLLER = FocusController;

// Dummy <input> element created for mobile keyboards
let mockDOMInput;
function initMockDOMInput() {
    // create temp input (for mobile keyboard)
    if (typeof mockDOMInput === 'undefined') {
        mockDOMInput = document.createElement('INPUT');
        mockDOMInput.setAttribute('type', 'text');
        mockDOMInput.setAttribute('id', '_pui_tempInput');
        mockDOMInput.setAttribute('style', 'position:fixed; left:-10px; top:-10px; width:0px; height: 0px;');
        document.body.appendChild(mockDOMInput);
    }
}
/**
 * An UI text object
 *
 * @class
 * @extends PIXI.UI.InputBase
 * @memberof PIXI.UI
 */
class TextInput extends FocusableWidget {
    /**
     * @param {PUXI.ITextInputOptions} options
     * @param {string} options.value Text content
     * @param {boolean} [options.multiLine=false] Multiline input
     * @param options.style {PIXI.TextStyle} Style used for the Text
     * @param options.background {(PIXI.UI.SliceSprite|PIXI.UI.Sprite)} will be used as background for input
     * @param [options.selectedColor='#ffffff'] {String|Array} Fill color of selected text
     * @param [options.selectedBackgroundColor='#318cfa'] {String} BackgroundColor of selected text
     * @param [options.width=150] {Number} width of input
     * @param [options.height=20] {Number} height of input
     * @param [options.padding=3] {Number} input padding
     * @param [options.paddingTop=0] {Number} input padding
     * @param [options.paddingBottom=0] {Number} input padding
     * @param [options.paddingLeft=0] {Number} input padding
     * @param [options.paddingRight=0] {Number} input padding
     * @param [options.tabIndex=0] {Number} input tab index
     * @param [options.tabGroup=0] {Number|String} input tab group
     * @param [options.maxLength=0] {Number} 0 = unlimited
     * @param [options.caretWidth=1] {Number} width of the caret
     * @param [options.lineHeight=0] {Number} 0 = inherit from text
     */
    constructor(options) {
        super(options);
        this.onKeyDown = (e) => {
            if (e.which === this.ctrlKey || e.which === this.cmdKey) {
                this.ctrlDown = true;
            }
            if (e.which === this.shiftKey) {
                this.shiftDown = true;
            }
            // FocusableWidget.onKeyDownImpl should've been called before this.
            if (e.defaultPrevented) {
                return;
            }
            if (e.which === 13) { // enter
                this.insertTextAtCaret('\n');
                e.preventDefault();
                return;
            }
            if (this.ctrlDown) {
                // Handle Ctrl+<?> commands
                if (e.which === 65) {
                    // Ctrl+A (Select all)
                    this.select();
                    e.preventDefault();
                    return;
                }
                else if (e.which === 90) {
                    // Ctrl+Z (Undo)
                    if (this.value != this._lastValue) {
                        this.valueEvent = this._lastValue;
                    }
                    this.setCaretIndex(this._lastValue.length + 1);
                    e.preventDefault();
                    return;
                }
            }
            if (e.which === 8) {
                // Handle backspace
                if (!this.deleteSelection()) {
                    if (this.caret._index > 0 || (this.chars.length === 1 && this.caret._atEnd)) {
                        if (this.caret._atEnd) {
                            this.valueEvent = this.value.slice(0, this.chars.length - 1);
                            this.setCaretIndex(this.caret._index);
                        }
                        else {
                            this.valueEvent = this.value.slice(0, this.caret._index - 1) + this.value.slice(this.caret._index);
                            this.setCaretIndex(this.caret._index - 1);
                        }
                    }
                }
                e.preventDefault();
                return;
            }
            if (e.which === 46) {
                // Delete selection
                if (!this.deleteSelection()) {
                    if (!this.caret._atEnd) {
                        this.valueEvent = this.value.slice(0, this.caret._index) + this.value.slice(this.caret._index + 1);
                        this.setCaretIndex(this.caret._index);
                    }
                }
                e.preventDefault();
                return;
            }
            else if (e.which === 37 || e.which === 39) {
                this.rdd = e.which === 37;
                if (this.shiftDown) {
                    if (this.hasSelection) {
                        const caretAtStart = this.selectionStart === this.caret._index;
                        if (caretAtStart) {
                            if (this.selectionStart === this.selectionEnd && this.rdd === this.caret._forward) {
                                this.setCaretIndex(this.caret._forward ? this.caret._index : this.caret._index + 1);
                            }
                            else {
                                const startindex = this.rdd ? this.caret._index - 1 : this.caret._index + 1;
                                this.selectRange(startindex, this.selectionEnd);
                                this.caret._index = Math.min(this.chars.length - 1, Math.max(0, startindex));
                            }
                        }
                        else {
                            const endIndex = this.rdd ? this.caret._index - 1 : this.caret._index + 1;
                            this.selectRange(this.selectionStart, endIndex);
                            this.caret._index = Math.min(this.chars.length - 1, Math.max(0, endIndex));
                        }
                    }
                    else {
                        const _i = this.caret._atEnd ? this.caret._index + 1 : this.caret._index;
                        const selectIndex = this.rdd ? _i - 1 : _i;
                        this.selectRange(selectIndex, selectIndex);
                        this.caret._index = selectIndex;
                        this.caret._forward = !rdd;
                    }
                }
                else {
                    // Navigation
                    // eslint-disable-next-line no-lonely-if
                    if (this.hasSelection) {
                        this.setCaretIndex(this.rdd ? this.selectionStart : this.selectionEnd + 1);
                    }
                    else {
                        this.setCaretIndex(this.caret._index + (this.rdd ? this.caret._atEnd ? 0 : -1 : 1));
                    }
                }
                e.preventDefault();
                return;
            }
            else if (this.multiLine && (e.which === 38 || e.which === 40)) {
                this.vrdd = e.which === 38;
                if (this.shiftDown) {
                    if (this.hasSelection) {
                        this.de.y = Math.max(0, Math.min(this.textHeightPX, this.de.y + (this.vrdd ? -this.lineHeight : this.lineHeight)));
                        this.updateClosestIndex(this.de, false);
                        // console.log(si, ei);
                        if (Math.abs(this.si - this.ei) <= 1) {
                            // console.log(si, ei);
                            this.setCaretIndex(this.sie ? this.si + 1 : this.si);
                        }
                        else {
                            this.caret._index = (this.eie ? this.ei + 1 : this.ei) + (this.caret._down ? -1 : 0);
                            this.selectRange(this.caret._down ? this.si : this.si - 1, this.caret._index);
                        }
                    }
                    else {
                        this.si = this.caret._index;
                        this.sie = false;
                        this.de.copyFrom(this.caret);
                        this.de.y = Math.max(0, Math.min(this.textHeightPX, this.de.y + (this.vrdd ? -this.lineHeight : this.lineHeight)));
                        this.updateClosestIndex(this.de, false);
                        this.caret._index = (this.eie ? this.ei + 1 : ei) - (this.vrdd ? 0 : 1);
                        this.selectRange(this.vrdd ? this.si - 1 : this.si, this.caret._index);
                        this.caret._down = !this.vrdd;
                    }
                }
                else if (this.hasSelection) {
                    this.setCaretIndex(this.vrdd ? this.selectionStart : this.selectionEnd + 1);
                }
                else {
                    this.ds.copyFrom(this.caret);
                    this.ds.y += this.vrdd ? -this.lineHeight : this.lineHeight;
                    this.ds.x += 1;
                    this.updateClosestIndex(this.ds, true);
                    this.setCaretIndex(this.sie ? this.si + 1 : this.si);
                }
                e.preventDefault();
                return;
            }
        };
        this.keyUpEvent = (e) => {
            if (e.which === this.ctrlKey || e.which === this.cmdKey)
                this.ctrlDown = false;
            if (e.which === this.shiftKey)
                this.shiftDown = false;
            this.emit('keyup', e);
            if (e.defaultPrevented) {
                return;
            }
        };
        this.copyEvent = (e) => {
            this.emit('copy', e);
            if (e.defaultPrevented) {
                return;
            }
            if (this.hasSelection) {
                const clipboardData = e.clipboardData || window.clipboardData;
                clipboardData.setData('Text', this.value.slice(this.selectionStart, this.selectionEnd + 1));
            }
            e.preventDefault();
        };
        this.cutEvent = (e) => {
            this.emit('cut', e);
            if (e.defaultPrevented) {
                return;
            }
            if (this.hasSelection) {
                this.copyEvent(e);
                this.deleteSelection();
            }
            e.preventDefault();
        };
        this.pasteEvent = (e) => {
            this.emit('paste', e);
            if (e.defaultPrevented) {
                return;
            }
            const clipboardData = e.clipboardData || window.clipboardData;
            this.insertTextAtCaret(clipboardData.getData('Text'));
            e.preventDefault();
        };
        this.inputEvent = (e) => {
            const c = mockDOMInput.value;
            if (c.length) {
                this.insertTextAtCaret(c);
                mockDOMInput.value = '';
            }
            e.preventDefault();
        };
        this.inputBlurEvent = (e) => {
            this.blur();
        };
        this.focus = () => {
            if (!this._isFocused) {
                super.focus();
                const l = `${this.contentContainer.worldTransform.tx}px`;
                const t = `${this.contentContainer.worldTransform.ty}px`;
                const h = `${this.contentContainer.height}px`;
                const w = `${this.contentContainer.width}px`;
                mockDOMInput.setAttribute('style', `position:fixed; left:${l}; top:${t}; height:${h}; width:${w};`);
                mockDOMInput.value = '';
                mockDOMInput.focus();
                mockDOMInput.setAttribute('style', 'position:fixed; left:-10px; top:-10px; width:0px; height: 0px;');
                this.innerContainer.cacheAsBitmap = false;
                mockDOMInput.addEventListener('blur', this.inputBlurEvent, false);
                document.addEventListener('keydown', this.onKeyDown, false);
                document.addEventListener('keyup', this.keyUpEvent, false);
                document.addEventListener('paste', this.pasteEvent, false);
                document.addEventListener('copy', this.copyEvent, false);
                document.addEventListener('cut', this.cutEvent, false);
                mockDOMInput.addEventListener('input', this.inputEvent, false);
                setTimeout(() => {
                    if (!this.caret.visible && !this.selection.visible && !this.multiLine) {
                        this.setCaretIndex(this.chars.length);
                    }
                }, 0);
            }
        };
        this.blur = () => {
            if (this._isFocused) {
                super.blur();
                this.ctrlDown = false;
                this.shiftDown = false;
                this.hideCaret();
                this.clearSelection();
                if (this.chars.length > 1) {
                    this.innerContainer.cacheAsBitmap = true;
                }
                mockDOMInput.removeEventListener('blur', this.inputBlurEvent);
                document.removeEventListener('keydown', this.onKeyDown);
                document.removeEventListener('keyup', this.keyUpEvent);
                document.removeEventListener('paste', this.pasteEvent);
                document.removeEventListener('copy', this.copyEvent);
                document.removeEventListener('cut', this.cutEvent);
                mockDOMInput.removeEventListener('input', this.inputEvent);
                mockDOMInput.blur();
            }
            if (!this.multiLine) {
                this.resetScrollPosition();
            }
        };
        this.setCaretIndex = (index) => {
            this.caret._atEnd = index >= this.chars.length;
            this.caret._index = Math.max(0, Math.min(this.chars.length - 1, index));
            if (this.chars.length && index > 0) {
                let i = Math.max(0, Math.min(index, this.chars.length - 1));
                let c = this.chars[i];
                if (c && c.wrapped) {
                    this.caret.x = c.x;
                    this.caret.y = c.y;
                }
                else {
                    i = Math.max(0, Math.min(index - 1, this.chars.length - 1));
                    c = this.chars[i];
                    this.caret.x = this.chars[i].x + this.chars[i].width;
                    this.caret.y = (this.chars[i].lineIndex * this.lineHeight) + (this.lineHeight - this.textHeight) * 0.5;
                }
            }
            else {
                this.caret.x = 0;
                this.caret.y = (this.lineHeight - this.textHeight) * 0.5;
            }
            this.scrollToPosition(this.caret);
            this.showCaret();
        };
        this.select = () => {
            this.selectRange(0, this.chars.length - 1);
        };
        this.selectWord = (wordIndex) => {
            let startIndex = this.chars.length;
            let endIndex = 0;
            for (let i = 0; i < this.chars.length; i++) {
                if (this.chars[i].wordIndex !== wordIndex) {
                    continue;
                }
                if (i < startIndex) {
                    startIndex = i;
                }
                if (i > endIndex) {
                    endIndex = i;
                }
            }
            this.selectRange(startIndex, endIndex);
        };
        this.selectRange = (startIndex, endIndex) => {
            if (startIndex > -1 && endIndex > -1) {
                const start = Math.min(startIndex, endIndex, this.chars.length - 1);
                const end = Math.min(Math.max(startIndex, endIndex), this.chars.length - 1);
                if (start !== this.selectionStart || end !== this.selectionEnd) {
                    this.hasSelection = true;
                    this.selection.visible = true;
                    this.selectionStart = start;
                    this.selectionEnd = end;
                    this.hideCaret();
                    this.updateSelectionGraphics();
                    this.updateSelectionColors();
                }
                this.focus();
            }
            else {
                this.clearSelection();
            }
        };
        this.clearSelection = () => {
            if (this.hasSelection) {
                // Remove color
                this.hasSelection = false;
                this.selection.visible = false;
                this.selectionStart = -1;
                this.selectionEnd = -1;
                this.updateSelectionColors();
            }
        };
        this.updateSelectionGraphics = () => {
            const c1 = this.chars[this.selectionStart];
            if (c1 !== undefined) {
                let cx = c1.x;
                let cy = c1.y;
                let w = 0;
                const h = this.textHeight;
                let cl = c1.lineIndex;
                this.selection.clear();
                for (let i = this.selectionStart; i <= this.selectionEnd; i++) {
                    const c = this.chars[i];
                    if (c.lineIndex != cl) {
                        this.drawSelectionRect(cx, cy, w, h);
                        cx = c.x;
                        cy = c.y;
                        cl = c.lineIndex;
                        w = 0;
                    }
                    w += c.width;
                }
                this.drawSelectionRect(cx, cy, w, h);
                this.innerContainer.addChildAt(this.selection, 0);
            }
        };
        this.drawSelectionRect = (x, y, w, h) => {
            this.selection.beginFill(`0x${this.selectedBackgroundColor.slice(1)}`, 1);
            this.selection.moveTo(x, y);
            this.selection.lineTo(x + w, y);
            this.selection.lineTo(x + w, y + h);
            this.selection.lineTo(x, y + h);
            this.selection.endFill();
        };
        initMockDOMInput();
        this.options = options;
        this._dirtyText = true;
        this.maxLength = options.maxLength || 0;
        this._value = this._lastValue = options.value || '';
        if (this.maxLength) {
            this._value = this._value.slice(0, this.maxLength);
        }
        this.chars = [];
        this.multiLine = options.multiLine !== undefined ? options.multiLine : false;
        this.color = options.style && options.style.fill ? options.style.fill : '#000000';
        this.selectedColor = options.selectedColor || '#ffffff';
        this.selectedBackgroundColor = options.selectedBackgroundColor || '#318cfa';
        this.tempText = new Text('1', options.style);
        this.textHeight = this.tempText.height;
        this.lineHeight = options.lineHeight || this.textHeight || this._height;
        this.tempText.destroy();
        // set cursor
        // this.container.cursor = "text";
        // selection graphics
        this.selection = new Graphics();
        this.selection.visible = false;
        this.selection._startIndex = 0;
        this.selection._endIndex = 0;
        // caret graphics
        this.caret = new Graphics();
        this.caret.visible = false;
        this.caret._index = 0;
        this.caret.lineStyle(options.caretWidth || 1, '#ffffff', 1);
        this.caret.moveTo(0, 0);
        this.caret.lineTo(0, this.textHeight);
        // var padding
        const paddingLeft = options.paddingLeft !== undefined ? options.paddingLeft : options.padding;
        const paddingRight = options.paddingRight !== undefined ? options.paddingRight : options.padding;
        const paddingBottom = options.paddingBottom !== undefined ? options.paddingBottom : options.padding;
        const paddingTop = options.paddingTop !== undefined ? options.paddingTop : options.padding;
        // insert text container (scrolling container)
        this.textContainer = new ScrollWidget({
            scrollX: !this.multiLine,
            scrollY: this.multiLine,
            dragScrolling: this.multiLine,
            expandMask: 2,
            softness: 0.2,
            overflowX: 40,
            overflowY: 40,
        }).setPadding(paddingLeft || 3, paddingTop || 3, paddingRight || 3, paddingBottom || 3).setLayoutOptions(new LayoutOptions(LayoutOptions.FILL_PARENT, LayoutOptions.FILL_PARENT));
        this.addChild(this.textContainer);
        if (this.multiLine) {
            this._useNext = this._usePrev = false;
            this.textContainer.dragRestrictAxis = 'y';
            this.textContainer.dragThreshold = 5;
            this.dragRestrictAxis = 'x';
            this.dragThreshold = 5;
        }
        // selection Vars
        this.sp = new Point(); // startposition
        this._sp = new Point();
        this.ds = new Point(); // dragStart
        this.de = new Point(); // dragend
        this.rdd = false; // Reverse drag direction
        this.vrdd = false; // vertical Reverse drag direction
        this.selectionStart = -1;
        this.selectionEnd = -1;
        this.hasSelection = false;
        this.t = performance.now(); // timestamp
        this.cc = 0; // click counter
        this.textLengthPX = 0;
        this.textHeightPX = 0;
        this.lineIndexMax = 0;
        this.ctrlDown = false;
        this.shiftDown = false;
        this.shiftKey = 16;
        this.ctrlKey = 17;
        this.cmdKey = 91;
        this.setupDrag();
    }
    setupDrag() {
        const event = new DragManager(this);
        event.onPress = (e, mouseDown) => {
            if (mouseDown) {
                const timeSinceLast = performance.now() - this.t;
                this.t = performance.now();
                if (timeSinceLast < 250) {
                    this.cc++;
                    if (this.cc > 1) {
                        this.select();
                    }
                    else {
                        this.innerContainer.toLocal(this.sp, undefined, this.ds, true);
                        this.updateClosestIndex(this.ds, true);
                        const c = this.chars[this.si];
                        if (c) {
                            if (c.wordIndex != -1) {
                                this.selectWord(c.wordIndex);
                            }
                            else {
                                this.selectRange(this.si, this.si);
                            }
                        }
                    }
                }
                else {
                    this.cc = 0;
                    this.sp.copyFrom(e.data.global);
                    this.innerContainer.toLocal(this.sp, undefined, this.ds, true);
                    if (this.chars.length) {
                        this.updateClosestIndex(this.ds, true);
                        this.setCaretIndex(this.sie ? this.si + 1 : this.si);
                    }
                }
            }
            e.data.originalEvent.preventDefault();
        };
        event.onDragMove = (e, offset) => {
            if (!this.chars.length || !this._isFocused) {
                return;
            }
            this.de.x = this.sp.x + offset.x;
            this.de.y = this.sp.y + offset.y;
            this.innerContainer.toLocal(this.de, undefined, this.de, true);
            this.updateClosestIndex(this.de, false);
            if (this.si < this.ei) {
                this.selectRange(this.sie ? this.si + 1 : this.si, this.eie ? this.ei : this.ei - 1);
                this.caret._index = this.eie ? this.ei : this.ei - 1;
            }
            else if (this.si > this.ei) {
                this.selectRange(this.ei, this.sie ? this.si : this.si - 1);
                this.caret._index = this.ei;
            }
            else if (this.sie === this.eie) {
                this.setCaretIndex(this.sie ? this.si + 1 : this.si);
            }
            else {
                this.selectRange(this.si, this.ei);
                this.caret._index = this.ei;
            }
            this.caret._forward = this.si <= this.ei;
            this.caret._down = offset.y > 0;
            this.scrollToPosition(this.de);
        };
    }
    get innerContainer() {
        return this.textContainer.innerContainer.insetContainer;
    }
    update() {
        if (this.width !== this._lastWidth) {
            this._lastWidth = this._width;
            if (this.multiLine) {
                this.updateText();
                if (this.caret.visible) {
                    this.setCaretIndex(this.caret._index);
                }
                if (this.hasSelection) {
                    this.updateSelectionGraphics();
                }
            }
        }
        // update text
        if (this._dirtyText) {
            this.updateText();
            this._dirtyText = false;
        }
    }
    updateText() {
        this.textLengthPX = 0;
        this.textHeightPX = 0;
        this.lineIndexMax = 0;
        let lineIndex = 0;
        const length = this._value.length;
        let x = 0;
        let y = (this.lineHeight - this.textHeight) * 0.5;
        let i = 0;
        // destroy excess chars
        if (this.chars.length > length) {
            for (i = this.chars.length - 1; i >= length; i--) {
                this.innerContainer.removeChild(this.chars[i]);
                this.chars[i].destroy();
            }
            this.chars.splice(length, this.chars.length - length);
        }
        // update and add chars
        let whitespace = false;
        let newline = false;
        let wordIndex = 0;
        let lastWordIndex = -1;
        let wrap = false;
        for (i = 0; i < this._value.length; i++) {
            if (whitespace || newline) {
                lastWordIndex = i;
                wordIndex++;
            }
            let c = this._value[i];
            whitespace = c === ' ';
            newline = c === '\n';
            if (newline) { // newline "hack". webgl render errors if \n is passed to text
                c = '';
            }
            let charText = this.chars[i];
            if (!charText) {
                charText = new Text(c, this.options.style);
                this.innerContainer.addChild(charText);
                this.chars.push(charText);
            }
            else {
                charText.text = c;
            }
            charText.scale.x = newline ? 0 : 1;
            charText.wrapped = wrap;
            wrap = false;
            if (newline || (this.multiLine && x + charText.width >= this._width - this.paddingLeft - this.paddingRight)) {
                lineIndex++;
                x = 0;
                y += this.lineHeight;
                if (lastWordIndex !== -1 && !newline) {
                    i = lastWordIndex - 1;
                    lastWordIndex = -1;
                    wrap = true;
                    continue;
                }
            }
            charText.lineIndex = lineIndex;
            charText.x = x;
            charText.y = y;
            charText.wordIndex = whitespace || newline ? -1 : wordIndex;
            x += charText.width;
            if (x > this.textLengthPX) {
                this.textLengthPX = x;
            }
            if (y > this.textHeightPX) {
                this.textHeightPX = y;
            }
        }
        this.lineIndexMax = lineIndex;
        // put caret on top
        this.innerContainer.addChild(this.caret);
        // recache
        if (this.innerContainer.cacheAsBitmap) {
            this.innerContainer.cacheAsBitmap = false;
            this.innerContainer.cacheAsBitmap = true;
        }
        this.textContainer.update();
    }
    updateClosestIndex(point, start) {
        let currentDistX = 99999;
        let currentIndex = -1;
        let atEnd = false;
        let closestLineIndex = 0;
        if (this.lineIndexMax > 0) {
            closestLineIndex = Math.max(0, Math.min(this.lineIndexMax, Math.floor(point.y / this.lineHeight)));
        }
        for (let i = 0; i < this.chars.length; i++) {
            const char = this.chars[i];
            if (char.lineIndex !== closestLineIndex) {
                continue;
            }
            const distX = Math.abs(point.x - (char.x + (char.width * 0.5)));
            if (distX < currentDistX) {
                currentDistX = distX;
                currentIndex = i;
                atEnd = point.x > char.x + (char.width * 0.5);
            }
        }
        if (start) {
            this.si = currentIndex;
            this.sie = atEnd;
        }
        else {
            this.ei = currentIndex;
            this.eie = atEnd;
        }
    }
    deleteSelection() {
        if (this.hasSelection) {
            this.value = this.value.slice(0, this.selectionStart) + this.value.slice(this.selectionEnd + 1);
            this.setCaretIndex(this.selectionStart);
            return true;
        }
        return false;
    }
    updateSelectionColors() {
        // Color charecters
        for (let i = 0; i < this.chars.length; i++) {
            if (i >= this.selectionStart && i <= this.selectionEnd) {
                this.chars[i].style.fill = this.selectedColor;
            }
            else {
                this.chars[i].style.fill = this.color;
            }
        }
    }
    scrollToPosition(pos) {
        this._sp.x = pos.x;
        this._sp.y = pos.y;
        if (this.multiLine && this._sp.y >= this.lineHeight) {
            this._sp.y += this.lineHeight;
        }
        this.textContainer.focusPosition(this._sp);
    }
    resetScrollPosition() {
        this._sp.set(0, 0);
        this.textContainer.focusPosition(this._sp);
    }
    hideCaret() {
        this.caret.visible = false;
        clearInterval(this.caretInterval);
    }
    showCaret() {
        this.clearSelection();
        clearInterval(this.caretInterval);
        this.caret.alpha = 1;
        this.caret.visible = true;
        this.caretInterval = setInterval(() => {
            this.caret.alpha = this.caret.alpha === 0 ? 1 : 0;
        }, 500);
    }
    insertTextAtCaret(c) {
        if (!this.multiLine && c.indexOf('\n') !== -1) {
            c = c.replace(/\n/g, '');
        }
        if (this.hasSelection) {
            this.deleteSelection();
        }
        if (!this.maxLength || this.chars.length < this.maxLength) {
            if (this.caret._atEnd) {
                this.valueEvent += c;
                this.setCaretIndex(this.chars.length);
            }
            else {
                const index = Math.min(this.chars.length - 1, this.caret._index);
                this.valueEvent = this.value.slice(0, index) + c + this.value.slice(index);
                this.setCaretIndex(index + c.length);
            }
        }
    }
    get valueEvent() {
        return this._value;
    }
    set valueEvent(val) {
        if (this.maxLength) {
            val = val.slice(0, this.maxLength);
        }
        if (this._value != val) {
            this.value = val;
            this.emit('change');
        }
    }
    get value() {
        return this._value;
    }
    set value(val) {
        if (this.maxLength) {
            val = val.slice(0, this.maxLength);
        }
        if (this._value != val) {
            this._lastValue = this._value;
            this._value = val;
            this._dirtyText = true;
            this.update();
        }
    }
    get text() {
        return this.value;
    }
    set text(value) {
        this.value = value;
    }
}
/*
 * Features:
 * multiLine, shift selection, Mouse Selection, Cut, Copy, Paste, Delete, Backspace, Arrow navigation, tabIndex
 *
 * Methods:
 * blur()
 * focus()
 * select() - selects all text
 * selectRange(startIndex, endIndex)
 * clearSelection()
 * setCaretIndex(index) moves caret to index
 *
 *
 * Events:
 * "change"
 * "blur"
 * "blur"
 * "focus"
 * "focusChanged" param: [bool]focus
 * "keyup" param: Event
 * "keydown" param: Event
 * "copy" param: Event
 * "paste" param: Event
 * "cut" param: Event
 * "keyup" param: Event
 */

/**
 * An UI sprite object
 *
 * @class
 * @extends PIXI.UI.UIBase
 * @memberof PIXI.UI
 * @param Texture {PIXI.Texture} The texture for the sprite
 * @param [Width=Texture.width] {number} Width of tilingsprite
 * @param [Height=Texture.height] {number} Height of tiling sprite
 */
class TilingSprite extends Widget {
    constructor(t, width, height) {
        const sprite = new extras.TilingSprite(t);
        super(width || sprite.width, height || sprite.height);
        this.sprite = sprite;
        this.contentContainer.addChild(this.sprite);
    }
    /**
     * Updates the text
     *
     * @private
     */
    update() {
        if (this.tint !== null) {
            this.sprite.tint = this.tint;
        }
        if (this.blendMode !== null) {
            this.sprite.blendMode = this.blendMode;
        }
        this.sprite.width = this._width;
        this.sprite.height = this._height;
    }
    get tilePosition() {
        return this.sprite.tilePosition;
    }
    set tilingPosition(val) {
        this.sprite.tilePosition = val;
    }
    get tileScale() {
        return this.sprite.tileScale;
    }
    set tileScale(val) {
        this.sprite.tileScale = val;
    }
}

/**
 * This ticker is an event-emitter that can be used for running periodic tasks
 * in the rendering loop. It emits the `update` event every animation frame.
 *
 * @memberof PUXI
 * @class
 * @extends PIXI.utils.EventEmitter
 */
class Ticker extends utils.EventEmitter {
    constructor(autoStart) {
        super();
        this._disabled = true;
        this._now = 0;
        this.DeltaTime = 0;
        this.Time = performance.now();
        this.Ms = 0;
        if (autoStart) {
            this.disabled = false;
        }
        Ticker.shared = this;
    }
    get disabled() {
        return this._disabled;
    }
    set disabled(val) {
        if (!this._disabled) {
            this._disabled = true;
        }
        else {
            this._disabled = false;
            Ticker.shared = this;
            this.update(performance.now(), true);
        }
    }
    /**
     * Updates the text
     *
     * @private
     */
    update(time) {
        Ticker.shared._now = time;
        Ticker.shared.Ms = Ticker.shared._now - Ticker.shared.Time;
        Ticker.shared.Time = Ticker.shared._now;
        Ticker.shared.DeltaTime = Ticker.shared.Ms * 0.001;
        Ticker.shared.emit('update', Ticker.shared.DeltaTime);
        Tween._update(Ticker.shared.DeltaTime);
        if (!Ticker.shared._disabled) {
            requestAnimationFrame(Ticker.shared.update);
        }
    }
    static on(event, fn, context) {
        Ticker.shared.on(event, fn, context);
    }
    static once(event, fn, context) {
        Ticker.shared.once(event, fn, context);
    }
    static removeListener(event, fn) {
        Ticker.shared.removeListener(event, fn);
    }
}
Ticker.shared = new Ticker(true);

/*!
 * @puxi/tween - v1.0.0
 * Compiled Sat, 21 Mar 2020 02:10:49 UTC
 *
 * @puxi/tween is licensed under the MIT License.
 * http://www.opensource.org/licenses/mit-license
 */

/**
 * Holds the information needed to perform a tweening operation. It is used internally
 * by `PUXI.tween.TweenManager`.
 *
 * @memberof PUXI.tween
 * @class
 * @template T
 */
class Tween$1 extends utils.EventEmitter {
    constructor(// eslint-disable-line max-params
    manager, key, startValue, endValue, erp, ease, observedValue, startTime, endTime, repeat = 1, flip = true) {
        super();
        /**
         * The tween-manager whose update loop handles this tween.
         * @member {PUXI.TweenManager}
         */
        this.manager = manager;
        /**
         * Unique id for this tweening operation
         * @member {string}
         */
        this.key = key;
        /**
         * Start value of interpolation
         * @member {T}
         */
        this.startValue = startValue;
        /**
         * End value of interpolation
         * @member {T}
         */
        this.endValue = endValue;
        /**
         * Linear interpolator on tween property.
         * @member {Erp}
         */
        this.erp = erp;
        /**
         * Easing function
         * @member {Ease}
         */
        this.ease = ease;
        /**
         * Object that is observed and the interpolated value to be stored in.
         * @member {T}
         */
        this.observedValue = observedValue;
        /**
         * @member {DOMHighResTimeStamp}
         * @readonly
         */
        this.startTime = startTime;
        /**
         * @member {DOMHighResTimeStamp}
         * @readonly
         */
        this.endTime = endTime;
        this._repeat = repeat;
        this._flip = flip;
        this._next = null;
        this._target = null;
        this._observedProperty = null;
        this.autoCreated = false;
    }
    /**
     * Updates the observed value.
     *
     * @param {DOMHighResTimeStamp} t - current time
     */
    update(t = performance.now()) {
        t = (t - this.startTime) / (this.endTime - this.startTime);
        t = Math.min(Math.max(t, 0), 1);
        if (this.ease) {
            t = this.ease(t);
        }
        // Update observed value
        this.observedValue = this.erp(this.startValue, this.endValue, Math.min(Math.max(t, 0), 1), this.observedValue);
        // Emit update event
        this.emit('update', this.observedValue, this.key);
        // Update target object (if any)
        if (this._target) {
            this._target[this._observedProperty] = this.observedValue;
        }
        // If cycle completed...
        if (t >= 1) {
            --this._repeat;
            this.emit('cycle', this);
            // Repeat tween if required
            if (this._repeat > 0) {
                if (this._flip) {
                    const { startValue: s, endValue: e } = this;
                    this.endValue = s;
                    this.startValue = e;
                }
                const duration = this.endTime - this.startTime;
                this.startTime += duration;
                this.endTime += duration;
                return;
            }
            // Initiate chained tween
            if (this._next) {
                this.manager.queue(this._next);
            }
            this.reset();
            // Cleanup after completion
            this.emit('complete', this);
            this.removeAllListeners();
        }
    }
    /**
     * Configures this tween to update the observed-property on a tween target object
     * each animation frame.
     * @template T
     * @param {PUXI.TweenTarget<T>} target - object on which property is being tweened
     * @param {string} observedProperty - name of property on target
     */
    target(target, observedProperty) {
        this._target = target;
        this._observedProperty = observedProperty;
        return this;
    }
    /**
     * Repeats this tween `repeat` no. of times again. If the tween is still running,
     * then this is no. of times it will again (not added to the previous repeat
     * count).
     *
     * Each time the tween is repeated, a `cycle` event is fired.
     *
     * By default, the repeat count of any tween is 1.
     *
     * @param {number} repeat - the repeat count
     * @param {boolean}[flip=true] - whether to switch start/end values each cycle
     * @returns {Tween<T>} - this tween, useful for method chaining
     */
    repeat(repeat, flip = true) {
        this._repeat = repeat;
        this._flip = flip;
        return this;
    }
    /**
     * Chains a tween that will run after this one finishes.
     *
     * @template W
     * @param {W} startValue
     * @param {W} endValue
     * @param {DOMHighResTimeStamp} duration
     * @param {PUXI.Erp<W>} erp
     * @param {PUXI.Ease}[ease]
     */
    chain(startValue, endValue, duration, erp, ease) {
        const next = (Tween$1.pool.pop() || new Tween$1());
        next.manager = this.manager;
        next.key = 0;
        next.startValue = startValue;
        next.endValue = endValue;
        next.startTime = this.endTime;
        next.endTime = next.startTime + duration;
        next.erp = erp;
        next.ease = ease;
        this._next = next;
        return next;
    }
    /**
     * Clears the tween's extra properties.
     */
    reset() {
        this.ease = null;
        this._repeat = 0;
        this._next = null;
        this._target = null;
        this._observedProperty = null;
    }
    /**
     * Called when a tween is complete and no references to it are held. This
     * will pool it (if auto-created).
     *
     * Custom tweens should override this.
     */
    destroy() {
        this.reset();
        if (this.autoCreated) {
            Tween$1.pool.push(this);
        }
    }
}
/**
 * Fired whenever the observed value is updated.
 * @event update
 * @param {T} observedValue
 * @param {number} key
 */
/**
 * Fired whenever the tween has "repeated" once.
 * @event cycle
 * @param {Tween} cxt
 */
/**
 * Fired when tween has finished. References to this tween should be removed.
 * @event complete
 * @param {Tween} cxt
 */
/**
 * Used for pooling.
 * @member {Array<TweenContext>}
 * @static
 */
Tween$1.pool = [];

// TODO: Prevent update loop from starting if there are no queued tweens.
let nextTweenKey = 0;
/**
 * @memberof PUXI.tween
 * @class
 */
class TweenManager {
    constructor(autoStart = true) {
        this.onUpdate = () => {
            for (const [, cxt] of this.tweenMap) {
                cxt.update();
            }
        };
        this.onTweenComplete = (cxt) => {
            this.tweenMap.delete(cxt.key);
            cxt.destroy();
        };
        this.tweenMap = new Map();
        if (autoStart) {
            this.start();
        }
    }
    /**
     * Initiates a tween from `startValue` to `endValue` for the given duration
     * using an interpolator.
     *
     * @template {T}
     * @param {T} startValue - value of tween property at start
     * @param {T} endValue - value of tween property at finish
     * @param {DOMHighResTimeStamp | number} duration - duration of tween in milliseconds
     * @param {PUXI.Erp<T>} erp - interpolator on tween property
     * @param {PUXI.Ease}[ease] - easing function
     */
    tween(startValue, endValue, duration, erp, ease) {
        const tweenCxt = (Tween$1.pool.pop() || new Tween$1());
        tweenCxt.autoCreated = true;
        tweenCxt.reset();
        tweenCxt.manager = this;
        tweenCxt.key = nextTweenKey++;
        tweenCxt.startValue = startValue;
        tweenCxt.endValue = endValue;
        tweenCxt.erp = erp;
        tweenCxt.ease = ease;
        tweenCxt.startTime = performance.now();
        tweenCxt.endTime = tweenCxt.startTime + duration;
        this.tweenMap.set(tweenCxt.key, tweenCxt);
        tweenCxt.on('complete', this.onTweenComplete);
        return tweenCxt;
    }
    /**
     * Queues the tween context so that it is updated every frame.
     *
     * @param {PUXI.Tween} context
     * @returns {PUXI.TweenManager} this manager, useful for method chaining
     */
    queue(context) {
        context.key = nextTweenKey++;
        this.tweenMap.set(context.key, context);
        context.on('complete', this.onTweenComplete);
        return this;
    }
    /**
     * Starts the update loop.
     */
    start() {
        if (this.isRunning) {
            return;
        }
        Ticker$1.shared.add(this.onUpdate);
        this.isRunning = true;
    }
    /**
     * Stops the update loop. This will prevent tweens from getting updated.
     */
    stop() {
        if (!this.isRunning) {
            return;
        }
        Ticker$1.shared.remove(this.onUpdate);
        this.isRunning = false;
    }
}

/**
 * @memberof PUXI
 * @typedef {Function} Ease
 * @param {number} t - interpolation parameter (b/w 0 and 1) that increases linearly
 * @returns {numeber} - output interpolation parameter (b/w 0 and 1)
 */
/**
 * Quadratic ease-in
 *
 * @memberof PUXI
 * @type Ease
 * @param {number} t
 * @returns {number}
 */
const EaseIn = (t) => t * t;
/**
 * Quadratic ease-out
 *
 * @memberof PUXI
 * @type Ease
 * @param {number} t
 * @returns {number}
 */
const EaseOut = (t) => (1 - t) * (1 - t);
/**
 * Quadratic ease-in & ease-out mixed!
 *
 * @memberof PUXI
 * @type Ease
 * @param {number} t
 * @returns {number}
 */
const EaseBoth = (t) => ((t <= 0.5)
    ? 2 * t * t
    : ((2 * ((t - 0.5) * (1.5 - t))) + 0.5));

/**
 * Defines a (linear) interpolator on a type `T`.
 *
 * @memberof PUXI
 * @typedef {Function} Erp
 * @template T
 * @param {T} startValue
 * @param {T} endValue
 * @param {number} t - interpolation parameter between 0 and 1
 * @param {T}[observedValue]
 */
/**
 * Interpolation function for number properties like alpha, rotation, component
 * position/scale/skew, elevation, etc.
 *
 * @memberof PUXI
 * @extends PUXI.Erp<number>
 * @param {number} startValue
 * @param {number} endValue
 * @param {number} t
 */
const NumberErp = (startValue, endValue, t) => ((1 - t) * startValue) + (t * endValue);
/**
 * Interpolation function for 2D vector properties like position, scale, skew, etc.
 *
 * @memberof PUXI
 * @extends PUXI.Erp<PIXI.Point>
 * @param {PIXI.Point} startValue
 * @param {PIXI.Point} endValue
 * @param {number} t
 * @param {PIXI.Point} observedValue
 */
const PointErp = (startValue, endValue, t, observedValue) => {
    if (!observedValue) {
        observedValue = new Point();
    }
    observedValue.x = ((1 - t) * startValue.x) + (t * endValue.x);
    observedValue.y = ((1 - t) * startValue.y) + (t * endValue.y);
    return observedValue;
};

var puxiTween = {
    __proto__: null,
    EaseBoth: EaseBoth,
    EaseIn: EaseIn,
    EaseOut: EaseOut,
    NumberErp: NumberErp,
    PointErp: PointErp,
    Tween: Tween$1,
    TweenManager: TweenManager,
    get nextTweenKey () { return nextTweenKey; }
};

export { ALIGN, AnchorLayout, AnchorLayoutOptions, BorderLayout, BorderLayoutOptions, Button, CheckBox, ClickManager, Ease, EventBroker, EventManager, FastLayout, FastLayoutOptions, Helpers, Insets, InteractiveGroup, LayoutOptions, MeasureMode, ScrollBar, ScrollManager, ScrollWidget, SliceSprite, Slider, SortableList, Sprite, Stage, TextInput, TextWidget, Ticker, TilingSprite, Widget, WidgetGroup, create, puxiTween as tween, wrapEase };
//# sourceMappingURL=puxi.mjs.map