API Documentation

for 0.0.1
Show:

File: src/js/Mobilizing/ui/TextField.js

import {Base} from '../Base';
import {getOrDefault} from '../helpers/Misc';
import {_Math} from '../helpers/Math';
import {Loader} from '../helpers/Loader';
import {Font} from '../scene/Font';
import {Color} from '../core/Color';
import {EventEmitter} from '../helpers/EventEmitter';
import {StyledLetter} from '../text/TextUtils';

/**
* Triggered when the canvas has been redrawn, useful to synchronise Texture update and canvas refresh
* @event drawn
*/
const EVT_DRAWN = "drawn";

class TextField extends Base
{
    /**
    * TextField.
    *
    * @class TextField
    * @constructor
    * @param {Object} params Config parameters
    * @param {String} params.text the String to display
    * @param {Font} params.font font Mobilizing.Font object
    * @param {Number} [params.fontSize = 20] fontSize font size
    * @param {Number} [params.lineMaxCharCount = 40] lineMaxCharCount the maximum number of characters a line should count
    * @param {Color} [params.color = Color.white] color the text color
    * @param {Color} params.backgroundColor backgroundColor backgroundColor of the text
    * @param {Number} [params.width = 300] width of the label
    * @param {Number} [params.height = 100] height of the label
    *
    * @example
    *    //TODO
    */
    constructor(params)
    {
        super(params);

        this.setName( getOrDefault(params, "name", undefined) );
        this._ready = false;

        this._styledLetters = [];
        this._maxCharCount = getOrDefault(params, "maxCharCount", 1000);
        this._maxLineCount = getOrDefault(params, "maxLineCount", 1);
        this._size = getOrDefault(params, "size", 20);
        this._color = getOrDefault(params, "color", Color.gray);
        this._backgroundColor = getOrDefault(params, "backgroundColor", Color.white);
        this._lineHeight = getOrDefault(params, "lineHeight", 1);

        this._margins = getOrDefault(params, "margins", 10);

        this._width = getOrDefault(params, "width", 300);
        this._height = getOrDefault(params, "height", 100);// to compute from font ?

        //this.mesh = new Mesh({primitive: "plane", width: 1, height:1, material:this.material});
        this._fontURL = getOrDefault(params, "fontURL", "fonts/Raleway-Regular.ttf");
        this._fontItalicURL = getOrDefault(params, "fontItalicURL", "fonts/Raleway-Regular-Italic.ttf");
        this._fontBoldURL = getOrDefault(params, "fontBoldURL", "fonts/Raleway-Bold.ttf");
        this._fontBoldItalicURL = getOrDefault(params, "fontBoldItalicURL", "fonts/Raleway-Bold-Italic.ttf");

        this._font = getOrDefault(params, "font", undefined);
        //to easily switch fonts in rendering process
        this._currentFont = this._font;

        this._cursorColor = getOrDefault(params, "cursorColor", this._color);
        this._cursorWidth = getOrDefault(params, "cursorWidth", 2);
        this._blinkTime = getOrDefault(params, "blinkTime", 300);
        this._blinkState = false;
        setInterval( this.onBlink.bind(this), this._blinkTime);

        this._cursorIndex = -1;//to get the position of the cursor from the letters!
        this._cursorX = 0;
        this._cursorY = 0;

        this._canvas = document.createElement("canvas");
        this._canvas.width = this._width;
        this._canvas.height = this._height;
        this._canvasContext = this._canvas.getContext("2d");

        this._canvas.events = new EventEmitter({scope:this._canvas});

        //        document.body.appendChild(this._canvas);
        //        this._canvas.style.position = "absolute";

        //loading font chain
        Loader.loadArrayBuffer(this._fontURL, (data) => {
            this._defaultFontFile = data;

            Loader.loadArrayBuffer(this._fontItalicURL, (fotnItalicData) => {
                this._defaultItalicFontFile = fotnItalicData;

                Loader.loadArrayBuffer(this._fontBoldURL, (fontBoldData) => {
                    this._defaultBoldFontFile = fontBoldData;

                    Loader.loadArrayBuffer(this._fontBoldItalicURL, (fontBoldItalicData) => {
                        this._defaultBoldItalicFontFile = fontBoldItalicData;

                        this.setup();
                    });
                });
            });
        });
    }

    /**
    * Setup bloc called after default font loading
    * @private
    * @method setup
    */
    setup()
    {
        this._defaultFont = new Font({fontFile: this._defaultFontFile});
        this._defaultItalicFont = new Font({fontFile: this._defaultItalicFontFile});
        this._defaultBoldFont = new Font({fontFile: this._defaultBoldFontFile});
        this._defaultBoldItalicFont = new Font({fontFile: this._defaultBoldItalicFontFile});

        this._currentFont = this._defaultFont;

        this.render();
    }

    /**
    * Set the font.
    * @method setFont
    * @param {Font} font Mobilizing font to use for the next letter
    */
    setFont(font)
    {
        this._currentFont = font;
    }
    /**
    * Set the size of the next letter
    * @method setSize
    * @param {Number} size the new size of the font
    */
    setSize(val)
    {
        this._size = val;
    }
    /**
    * Set the color of the next letter
    * @method setColor
    * @param {Color} color the new Mobilizing Color
    */
    setColor(val)
    {
        this._color = val;
    }

    /**
    * add a letter to the field content. Styles (font, color, etc) should be defined before calling this method.
    * @method setText
    * @param {String} letter the letter to add
    */
    addLetter(val)
    {
        let char = val.charCodeAt(0);
        let letter = String.fromCharCode(char);
        //manage new line feed
        if(val === "\n")
        {
            letter = val;
        }

        let styledLetter = new StyledLetter({letter: letter,
                                             font: this._currentFont,
                                             size: this._size,
                                             color: this._color
                                            });
        this._styledLetters.splice(this._cursorIndex+1, 0, styledLetter);

        this.moveCursorForward();

        this.render();
    }

    /**
    * Delete the letter currently before the cursor (or under selection when implemented)
    * @method delete
    */
    delete()
    {
        if(this._cursorIndex >= 0)
        {
            this._styledLetters.splice(this._cursorIndex, 1);
            //console.log("delete",this._cursorIndex,this._styledLetters);
            this.moveCursorBack();
        }
    }

    /**
    * Cursor blink callback
    * @method onBlink
    * @private
    */
    onBlink()
    {
        this._blinkState = !this._blinkState;
        //console.log("blink",this._blinkState);
        this.render();
    }

    /**
    * Moves the cursor to the next letter
    * @method moveCursorForward
    */
    moveCursorForward()
    {
        this._cursorIndex++;
        if(this._cursorIndex > this._styledLetters.length-1)
        {
            this._cursorIndex = this._styledLetters.length-1;
        }
        if(this._styledLetters.length === 0)
        {
            this._cursorIndex = -1;
        }
        this.render();
        //console.log("++this._cursorIndex",this._cursorIndex);
    }

    /**
    * Moves the cursor to the pevious letter
    * @method moveCursorBack
    */
    moveCursorBack()
    {
        this._cursorIndex--;
        if(this._cursorIndex < -1)
        {
            this._cursorIndex = -1;
        }
        this.render();
        //console.log("--this._cursorIndex",this._cursorIndex);
    }
    /**
    * Moves the cursor to the given index of the letter
    * @method moveCursorTo
    * @param {Number} index
    */
    moveCursorTo(index)
    {
        if(index >= -1 && index < this._styledLetters.length){
            this._cursorIndex = index;
            this.render();
        }
    }
    /**
    * Pick the letter situated under the given x,y coordinates
    * @method pickLetter
    * @param {Number} x
    * @param {Number} y
    */
    pickLetter(x,y)
    {
        for(let i=0; i<this._styledLetters.length; i++)
        {
            let el = this._styledLetters[i];
            let bbox = el.path.getBoundingBox();
            let rect = [{x:bbox.x1, y:bbox.y1}, {x:bbox.x2, y:bbox.y1},
                        {x:bbox.x2, y:bbox.y2}, {x:bbox.x1, y:bbox.y2}];

            if(_Math.pointIsInside(x,y, rect))
            {
                return {index:i, letter:this._styledLetters[i]};
            }
        }

        return null;
    }

    /**
    * Renders the canvas
    * @method render
    */
    render()
    {
        //background color
        this._canvasContext.fillStyle = "#" + this._backgroundColor.getHexString();
        this._canvasContext.fillRect(0,0, this._width, this._height);

        //x position of drawing (letter pos in x)
        var letterXOffset = this._margins;
        //y position of drawing (letter pos in y, or baseline)
        var lineYOffset = this._margins;

        for(let i=0; i<this._styledLetters.length; i++)
        {
            if(i < this._maxCharCount)
            {
                let el = this._styledLetters[i];

                //is it the 1st run ?  place the baseline to the margin + font size
                if(lineYOffset === this._margins)
                {
                    lineYOffset += el.size;
                }

                //test for new line from canvas width limit and reset offsets and add a new line to lineCount
                let tempWidth = letterXOffset + el.width;

                if(tempWidth > this._width - this._margins)
                {
                    lineYOffset += el.size;
                    letterXOffset = this._margins;
                }
                //test for new line feed
                if(el.letter === "\n")
                {
                    lineYOffset += el.size;
                    letterXOffset = this._margins;
                }

                el.setX(letterXOffset);
                el.setY(lineYOffset);
                el.update();//refresh
                el.path.draw(this._canvasContext);

                //update the x offset for next el
                letterXOffset += el.width;
            }
        }

        //draw the cursor
        if(this._blinkState)
        {
            let el = this._styledLetters[this._cursorIndex];

            if(el)
            {
                let boundingBox = el.path.getBoundingBox();

                this._canvasContext.beginPath();

                //console.log(el);

                if(el.height < 1)//manage blank space
                {
                    this._canvasContext.moveTo(el.x + el.width, el.y);
                    this._canvasContext.lineTo(el.x + el.width, el.y);
                    this._canvasContext.lineTo(el.x + el.width, el.y - this._size);
                }
                else //use path boundingBox
                {
                    this._canvasContext.moveTo(boundingBox.x2, boundingBox.y1);
                    this._canvasContext.lineTo(boundingBox.x2, boundingBox.y1);
                    this._canvasContext.lineTo(boundingBox.x2, boundingBox.y2);
                }
            }
            else //we don't have a letter, it's the start of the text
            {
                this._canvasContext.beginPath();
                this._canvasContext.moveTo(this._margins, this._margins);
                this._canvasContext.lineTo(this._margins, this._margins);
                this._canvasContext.lineTo(this._margins, this._margins + this._size);
            }

            this._canvasContext.lineWidth = this._cursorWidth;
            this._canvasContext.strokeStyle = "#" + this._cursorColor.getHex();
            this._canvasContext.stroke();
        }

        //emit a custom event on the canvas to refresh texture when used
        this._canvas.events.trigger(EVT_DRAWN);
    }

    /**
    * Get canvas width
    * @method getWidth
    * @return {Number} canvas width
    */
    getWidth()
    {
        return this._width;
    }
    /**
    * Get canvas height
    * @method getHeight
    * @return {Number} canvas height
    */
    getHeight()
    {
        return this._height;
    }

    /**
    * Get the canvas
    * @method getCanvas
    * @return {Canvas} canvas
    */
    getCanvas()
    {
        return this._canvas;
    }
}

// module exports
export { TextField };