Source: pixi/tooltip.js

pixi/tooltip.js

import Theme from './theme.js'
import AbstractPopup from './abstractpopup.js'

/**
 * Class that represents a PixiJS Tooltip.
 *
 * @example
 * // Create the app
 * const app = new PIXIApp({
 *     view: canvas,
 *     width: 900,
 *     height: 250
 * }).setup().run()
 *
 * // Add an DisplayObject to the app
 * const circle = new PIXI.Graphics()
 * circle.beginFill(0x5251a3)
 * circle.drawCircle(50, 50, 40)
 * app.scene.addChild(circle)
 *
 * const tooltip = new Tooltip({
 *     object: circle,
 *     container: app.scene,
 *     content: 'Das Gesetz ist der Freund des Schwachen.'
 * })
 *
 * @class
 * @extends AbstractPopup
 * @see {@link https://www.iwm-tuebingen.de/iwmbrowser/lib/pixi/tooltip.html|DocTest}
 */
export default class Tooltip extends AbstractPopup {
    /**
     * Creates an instance of a Tooltip.
     *
     * @constructor
     * @param {object} [opts] - An options object to specify to style and behaviour of the tooltip.
     * @param {number} [opts.minWidth=0] - The minimum width of the tooltip.
     * @param {number} [opts.minHeight=0] - The minimum height of the tooltip.
     * @param {number} [opts.padding=Theme.padding / 2] - The inner spacing of the tooltip.
     * @param {PIXI.DisplayObject} opts.object - The object, where the tooltip should be displayed.
     * @param {PIXI.DisplayObject} [opts.container=object] - The container where the tooltip should be attached to.
     * @param {number} [opts.offsetLeft=8] - The horizontal shift of the tooltip.
     * @param {number} [opts.offsetTop=-8] - The vertical shift of the tooltip.
     * @param {number} [opts.delay=0] - A delay, after which the tooltip should be opened.
     */
    constructor(opts = {}) {
        const theme = Theme.fromString(opts.theme)

        opts = Object.assign(
            {},
            {
                minWidth: 0,
                minHeight: 0,
                padding: theme.padding / 2,
                object: null,
                container: null,
                offsetLeft: 8,
                offsetTop: -8,
                delay: 0
            },
            opts
        )

        opts.container = opts.container || opts.object

        super(opts)

        // setup
        //-----------------
        this.setup()

        // layout
        //-----------------
        this.layout()
    }

    /**
     * Creates children and instantiates everything.
     *
     * @private
     * @return {Tooltip} A reference to the tooltip for chaining.
     */
    setup() {
        super.setup()

        // bind events this
        //-----------------
        this.interactive = true

        let mouseoverTooltip = false

        this.on('mouseover', e => {
            mouseoverTooltip = true
        })

        this.on('mouseout', e => {
            mouseoverTooltip = false
            if (!mouseoverObject) {
                this.hide(() => {
                    this.opts.container.removeChild(this)
                })
            }
        })

        // bind events object
        //-----------------
        const object = this.opts.object
        object.interactive = true

        let mouseoverObject = false

        object.on('mouseover', e => {
            this.timeout = window.setTimeout(() => {
                mouseoverObject = true
                this.visible = true
                this.opts.container.addChild(this)
                this.setPosition(e)
            }, this.opts.delay * 1000)
        })

        object.on('mousemove', e => {
            if (mouseoverObject) {
                this.setPosition(e)
            }
        })

        object.on('mouseout', e => {
            mouseoverObject = false
            window.clearTimeout(this.timeout)
            if (!mouseoverTooltip) {
                this.hide(() => {
                    this.opts.container.removeChild(this)
                })
            }
        })

        return this
    }

    /**
     * Calculates and sets the position of the tooltip.
     *
     * @private
     * @return {Tooltip} A reference to the tooltip for chaining.
     */
    setPosition(e) {
        const position = e.data.getLocalPosition(this.opts.container)

        this.x = position.x + this.opts.offsetLeft
        this.y = position.y + this.opts.offsetTop - this.height

        return this
    }
}