Source: pixi/abstractpopup.js

pixi/abstractpopup.js

import Theme from './theme.js'

/**
 * Class that represents a PixiJS AbstractPopup.
 * The class is used for various other Popup-like classes
 * like Popup, Message, Tooltip...
 *
 * @class
 * @abstract
 * @extends PIXI.Graphics
 * @see {@link http://pixijs.download/dev/docs/PIXI.Graphics.html|PIXI.Graphics}
 */
export default class AbstractPopup extends PIXI.Graphics {
    
    /**
     * Creates an instance of an AbstractPopup (only for internal use).
     * 
     * @constructor
     * @param {object} [opts] - An options object to specify to style and behaviour of the popup.
     * @param {number} [opts.id=auto generated] - The id of the popup.
     * @param {number} [opts.x=0] - The x position of the popup. Can be also set after creation with popup.x = 0.
     * @param {number} [opts.y=0] - The y position of the popup. Can be also set after creation with popup.y = 0.
     * @param {string|Theme} [opts.theme=dark] - The theme to use for this popup. Possible values are dark, light, red
     *     or a Theme object.
     * @param {string|number|PIXI.Text} [opts.header] - The heading inside the popup as a string, a number (will be
     *     converted to a text) or as a PIXI.Text object.
     * @param {string|number|PIXI.DisplayObject} [opts.content] - A text, a number (will be converted to a text) or
     *     an PIXI.DisplayObject as the content of the popup.
     * @param {number} [opts.minWidth=320] - The minimum width of the popup.
     * @param {number} [opts.minHeight=130] - The minimum height of the popup.
     * @param {number} [opts.padding=Theme.padding] - The inner spacing (distance from header and content) the the border.
     * @param {number} [opts.fill=Theme.fill] - The color of the button background as a hex value.
     * @param {number} [opts.fillAlpha=Theme.fillAlpha] - The alpha value of the background.
     * @param {number} [opts.stroke=Theme.stroke] - The color of the border as a hex value.
     * @param {number} [opts.strokeWidth=Theme.strokeWidth] - The width of the border in pixel.
     * @param {number} [opts.strokeAlpha=Theme.strokeAlpha] - The alpha value of the border.
     * @param {object} [opts.headerStyle=Theme.textStyleLarge] - A textstyle object for the styling of the header. See PIXI.TextStyle
     *     for possible options.
     * @param {object} [opts.textStyle=Theme.textStyleSmall] - A textstyle object for the styling of the text. See PIXI.TextStyle
     *     for possible options.
     * @param {number} [opts.radius=Theme.radius] - The radius of the four corners of the popup (which is a rounded rectangle).
     * @param {hiddenCallback} [opts.onHidden] - Executed when the popup gets hidden.
     * @param {boolean} [opts.visible=true] - Is the popup initially visible (property visible)?
     * @param {string} [opts.orientation] - When set to portrait, the popup cannot be displayed in landscape mode. When set
     *     to landscape, the popup cannot be displayed in portrait mode.
     */
    constructor(opts = {}) {

        super()
        
        const theme = Theme.fromString(opts.theme)
        this.theme = theme

        this.opts = Object.assign({}, {
            id: PIXI.utils.uid(),
            x: 0,
            y: 0,
            header: null,                       // null or null
            content: null,                      // null or String or PIXI.DisplayObject
            minWidth: 320,
            minHeight: 130,
            maxWidth: null,
            padding: theme.padding,
            fill: theme.fill,
            fillAlpha: theme.fillAlpha,
            stroke: theme.stroke,
            strokeWidth: theme.strokeWidth,
            strokeAlpha: theme.strokeAlpha,
            headerStyle: theme.textStyleLarge,
            textStyle: theme.textStyleSmall,
            radius: theme.radius,
            onHidden: null,
            visible: true,
            orientation: null
        }, opts)

        this.id = this.opts.id

        this.headerStyle = new PIXI.TextStyle(this.opts.headerStyle)
        this.textStyle = new PIXI.TextStyle(this.opts.textStyle)

        if (this.opts.maxWidth) {
            this.headerStyle.wordWrap = true
            this.headerStyle.wordWrapWidth = this.opts.maxWidth - (2 * this.opts.padding)

            this.textStyle.wordWrap = true
            this.textStyle.wordWrapWidth = this.opts.maxWidth - (2 * this.opts.padding)
        }

        this.alpha = 0
        this.visible = this.opts.visible

        this._header = null
        this._content = null

        // position
        this.x = this.opts.x
        this.y = this.opts.y

        // padding
        this.innerPadding = this.opts.padding * 1.5
        
        // interaction
        //-----------------
        this.interactive = true
        this.on('added', e => {
            this.show()
        })
    }
    
    /**
     * Creates the framework and instantiates everything.
     * 
     * @private
     * @return {AbstractPopup} A reference to the popup for chaining.
     */
    setup() {

        // position
        //-----------------
        this.sy = this.opts.padding

        // header
        //-----------------
        if (this.opts.header != null) {

            let header = null

            if (this.opts.header instanceof PIXI.Text) {
                header = this.opts.header
            } else if (typeof this.opts.header === 'number') {
                header =  new PIXI.Text(this.opts.header.toString(), this.headerStyle)
            } else {
                header =  new PIXI.Text(this.opts.header, this.headerStyle)
            }

            header.x = this.opts.padding
            header.y = this.sy

            this.addChild(header)

            this.sy += header.height

            this._header = header
        }

        if (this.opts.header && this.opts.content) {
            this.sy += this.innerPadding
        }

        // content
        //-----------------
        if (this.opts.content != null) {

            let content = null

            if (typeof this.opts.content === 'string') {
                content = new PIXI.Text(this.opts.content, this.textStyle)
            } else if (typeof this.opts.content === 'number') {
                content = new PIXI.Text(this.opts.content.toString(), this.textStyle)
            } else {
                content = this.opts.content
            }

            content.x = this.opts.padding
            content.y = this.sy

            this.sy += content.height

            this.addChild(content)

            this._content = content
        }

        return this
    }
    
    /**
     * Should be called to refresh the layout of the popup. Can be used after resizing.
     * 
     * @return {AbstractPopup} A reference to the popup for chaining.
     */
    layout() {
        
        // wanted width & wanted height
        //-----------------
        const padding = this.opts.padding
        const size = this.getInnerSize()
        const width = size.width + (2 * padding)
        const height = size.height + (2 * padding)

        this.wantedWidth = Math.max(width, this.opts.minWidth)
        this.wantedHeight = Math.max(height, this.opts.minHeight)
        
        if (this.opts.maxWidth) {
            this.wantedWidth = Math.min(this.wantedWidth, this.opts.maxWidth)
        }

        if (this.opts.radius * 2 > this.wantedWidth) {
            this.wantedWidth = this.opts.radius * 2
        }

        if (this.opts.radius * 2 > this.wantedHeight) {
            this.wantedHeight = this.opts.radius * 2
        }

        switch (this.opts.orientation) {
            case 'portrait':
                if (this.wantedWidth > this.wantedHeight) {
                    this.wantedHeight = this.wantedWidth
                }
                break
            case 'landscape':
                if (this.wantedHeight > this.wantedWidth) {
                    this.wantedWidth = this.wantedHeight
                }
                break
        }

        this.draw()

        return this
    }
    
    /**
     * Draws the canvas.
     * 
     * @private
     * @return {AbstractPopup} A reference to the popup for chaining.
     */
    draw() {

        const square = Math.round(this.wantedWidth) === Math.round(this.wantedHeight)
        const diameter = Math.round(this.opts.radius * 2)

        this.clear()
        this.lineStyle(this.opts.strokeWidth, this.opts.stroke, this.opts.strokeAlpha)
        this.beginFill(this.opts.fill, this.opts.fillAlpha)
        if (square && diameter === this.wantedWidth) {
            this.drawCircle(this.wantedWidth / 2, this.wantedHeight / 2, this.opts.radius)
        } else {
            this.drawRoundedRect(0, 0, this.wantedWidth, this.wantedHeight, this.opts.radius)
        }
        this.endFill()

        return this
    }
    
    /**
     * Calculates the size of the children of the AbstractPopup.
     * Cannot use getBounds() because it is not updated when children
     * are removed.
     * 
     * @private
     * @returns {object} An JavaScript object width the keys width and height.
     */
    getInnerSize() {

        let width = 0
        let height = 0

        if (this._header) {
            width = this._header.width
            height = this._header.height
        }

        if (this._header && this._content) {
            height += this.innerPadding
        }

        if (this._content) {
            width = Math.max(width, this._content.width)
            height += this._content.height
        }

        return {width, height}
    }
    
    /**
     * Shows the popup (sets his alpha values to 1).
     * 
     * @param {callback} [cb] - Executed when show animation was completed.
     * @return {AbstractPopup} A reference to the popup for chaining.
     */
    show(cb) {

        TweenLite.to(this, this.theme.fast, {
            alpha: 1,
            onComplete: () => {
                if (cb) {
                    cb.call(this)
                }
            }
        })

        return this
    }
    
    /**
     * Hides the popup (sets his alpha values to 0).
     * 
     * @param {callback} [cb] - Executed when hide animation was completed.
     * @return {AbstractPopup} A reference to the popup for chaining.
     */
    hide(cb) {

        TweenLite.to(this, this.theme.fast, {
            alpha: 0,
            onComplete: () => {
                this.visible = false
                if (cb) {
                    cb.call(this)
                }
            }
        })

        if (this.opts.onHidden) {
            this.opts.onHidden.call(this, this)
        }

        return this
    }

    /**
     * Sets or gets the header. The getter always returns a PIXI.Text object. The setter can receive
     * a string, a number or a PIXI.Text object.
     * 
     * @member {string|number|PIXI.Text}
     */
    get header() {
        return this._header
    }
    set header(value) {
        if (this._header) {
            this._header.destroy()
        }
        this.opts.header = value
        this.setup().layout()
    }
    
    /**
     * Sets or gets the content. The getter always returns an PIXI.DisplayObject. The setter can receive
     * a string, a number or a PIXI.DisplayObject.
     * 
     * @member {string|number|PIXI.DisplayObject}
     */
    get content() {
        return this._content
    }
    set content(value) {
        if (this._content) {
            this._content.destroy()
        }
        this.opts.content = value
        this.setup().layout()
    }
}