Source: controls/TextArea.js

var InputControl = require('./InputControl');

/**
 * A text entry control that allows users to enter and edit multiple lines of
 * uniformly-formatted text with the ability to scroll.
 *
 * @class TextInput
 * @extends GOWN.InputControl
 * @memberof GOWN
 * @constructor
 * @param [theme] theme for the text area {GOWN.Theme}
 * @param [skinName=TextArea.SKIN_NAME] name of the text area skin {String}
 */
function TextArea(theme, skinName) {
    // show and load background image as skin (exploiting skin states)
    /**
     * The skin name
     *
     * @type String
     * @default TextArea.SKIN_NAME
     */
    this.skinName = skinName || TextArea.SKIN_NAME;

    /**
     * The valid text area states
     *
     * @private
     * @type String[]
     * @default InputControl.stateNames
     */
    this._validStates = this._validStates || InputControl.stateNames;

    InputControl.call(this, theme);

    /**
     * @private
     * @type PIXI.Point
     */
    this._fromPos = new PIXI.Point(0, 0);

    /**
     * @private
     * @type PIXI.Point
     */
    this._toPos = new PIXI.Point(0, 0);

    /**
     * @private
     * @type PIXI.Point
     */
    this._fromText = new PIXI.Point(0, 0);

    /**
     * @private
     * @type PIXI.Point
     */
    this._toText = new PIXI.Point(0, 0);
}

TextArea.prototype = Object.create(InputControl.prototype);
TextArea.prototype.constructor = TextArea;
module.exports = TextArea;

/**
 * Default text area skin name
 *
 * @static
 * @final
 * @type String
 */
TextArea.SKIN_NAME = 'text_input';

/**
 * Update the selection
 *
 * @private
 */
TextArea.prototype.updateSelectionBg = function() {
    var start = this.selection[0],
        end = this.selection[1];
    this.selectionBg.clear();
    if (start === end) {
        return;
    }
    if (start < end) {
        this._drawSelectionBg(start, end);
    } else if (start > end) {
        this._drawSelectionBg(end, start);
    }
    this.selectionBg.x = this.pixiText.x;
    this.selectionBg.y = this.pixiText.y;
};

/**
 * Calculate position in Text
 */

/**
 * Calculate position in Text
 *
 * @param textPos Position in the text {Number}
 * @param [position] Position object that gets returned {PIXI.Point}
 * @returns {PIXI.Point} returns the Line and Position in line
 */
TextArea.prototype.textToLinePos = function(textPos, position) {
    var lines = this.getLines();
    var x = 0;
    for (var y = 0; y < lines.length; y++) {
        var lineLength = lines[y].length;
        if (lineLength < textPos) {
            textPos -= lineLength + 1;
        } else {
            x = textPos;
            break;
        }
    }

    if (!position) {
        position = new PIXI.Point(x, y);
    } else {
        position.x = x;
        position.y = y;
    }
    return position;
};

/**
 * New selection over multiple lines
 *
 * @param fromTextPos Start position {Number}
 * @param toTextPos End position {Number}
 * @private
 */
TextArea.prototype._drawSelectionBg = function (fromTextPos, toTextPos) {
    this.textToPixelPos(fromTextPos, this._fromPos);
    this.textToPixelPos(toTextPos, this._toPos);

    this.selectionBg.beginFill(0x0080ff);
    if (this._toPos.y === this._fromPos.y) {
        this.selectionBg.drawRect(
            this._fromPos.x,
            this._fromPos.y,
            this._toPos.x - this._fromPos.x,
            this.lineHeight());
        return;
    }

    this.textToLinePos(fromTextPos, this._fromText);
    this.textToLinePos(toTextPos, this._toText);
    var lines = this.getLines();
    // draw till the end of the line
    var startPos = this._fromText.x;
    for (var i = this._fromText.y; i < this._toText.y; i++) {
        var text = lines[i];
        this.selectionBg.drawRect(
            startPos > 0 ? this._fromPos.x : 0,
            i * this.lineHeight(),
            this.textWidth(text.substring(startPos, text.length)),
            this.lineHeight());
        startPos = 0;
    }
    this.selectionBg.drawRect(0,
        this._toPos.y,
        this._toPos.x,
        this.lineHeight());
};

/**
 * Get the text lines as an array
 *
 * @returns {Array|*} Returns an array with one text line per array element
 */
TextArea.prototype.getLines = function() {
    var wrappedText = this.pixiText.wordWrap(this.text);
    return wrappedText.split(/(?:\r\n|\r|\n)/);
};

/**
 * Width of the text area
 *
 * @name GOWN.TextArea#label
 * @type Number
 */
Object.defineProperty(InputControl.prototype, 'width', {
    get: function () {
        return this._width;
    },
    set: function(value) {
        this._width = value;
        this.minWidth = Math.min(value, this.minWidth);
        if (this.pixiText) {
            this.pixiText.style.wordWrapWidth = value - this.textOffset.x * 2;
            this._cursorNeedsUpdate = true;
            this._selectionNeedsUpdate = true;
        }
    }
});

/**
 * Set the text style
 *
 * @name GOWN.TextArea#style
 * @type PIXI.TextStyle
 */
Object.defineProperty(TextArea.prototype, 'style', {
    get: function() {
        return this.textStyle;
    },
    set: function(style) {
        this.cursorStyle = style;
        if (this.cursor) {
            this.cursor.style = style;
        }
        style = style.clone();
        style.wordWrap = true;
        if (!style.wordWrapWidth && this.textOffset && this.width) {
            style.wordWrapWidth = this.width - this.textOffset.x * 2;
        }
        this.textStyle = style;
        if (this.pixiText) {
            this.pixiText.style = style;
        }
        this._cursorNeedsUpdate = true;
    }
});