Source: Label.js

import * as BAGEL from "./index.js";

/**
 *  A Label draws text on the canvas of a {@link Game}.
 *  <br/>
 *  Labels must be added to {@link Group|Groups}, similar to {@link Sprite|Sprites}, to appear in a Game.
 */
class Label
{
	/**
	 * Initialize default values for all properties.
	 */
	constructor()
	{
		this.text = " ";

		this.position = new BAGEL.Vector();
		this.alignment = "left"; // "left" | "center" | "right"

		// style options, configured by setProperties method		
		this.visible = true;

		this.fontName = "Arial Black";
		this.fontSize = 32;
		this.fontColor = "#00FFFF";

		this.drawBorder = true;
		this.borderSize = 1;
		this.borderColor = "black";

		this.drawShadow = true;
		this.shadowColor = "black";
		this.shadowSize = 4;

		// extra graphics options
		this.visible = true;
		this.opacity = 1.00;

		this.lineHeight = 1.5;

		// list of Actions: functions applied to object over time
		this.actionList = [];
	}

    /**
     * Set the location of this label.
     * @param {number} x - the x coordinate of the text, corresponding to the location specified by the "alignment" parameter
     * @param {number} x - the y coordinate of the baseline of the text
     * @param {string} alignment - one of: "left", "center", "right"; determines what location of the text that the x coordinate refers to 
     */	
	setPosition(x,y, alignment="left")
	{
		this.position.setValues(x, y);
		this.alignment = alignment;
	}

	/**
	 * Set the text displayed by this label.
	 * @param {string} text - the text displayed by this label
	 */
	setText(text)
	{
		this.text = text;
	}

	/**
	 * Set the style properties for displaying text.
	 * <br/>
	 * Properties that can be set include:
	 * <ul>
	 * <li> <tt>fontName</tt> (string, e.g. "Arial Black")
	 * <li> <tt>drawBorder</tt> and <tt>drawShadow</tt> (boolean)
	 * <li> <tt>fontSize</tt>, <tt>borderSize</tt>, and <tt>shadowSize</tt> (number)
	 * <li> <tt>fontColor</tt>, <tt>borderColor</tt>, and <tt>shadowColor</tt> (string, e.g. "red" or "#FF0000")
	 * </ul>
	 * Properties are specified with an object.
	 * <br/>
	 * For example: <tt>{ "fontSize" : 48, "fontColor" : "#8800FF", "drawShadow" : false }</tt>
	 * @param {object} properties - an object containing property name and value pairs
	 */ 
	setProperties( properties )
	{
		for (let name in properties)
		{
			if ( this.hasOwnProperty(name) )
				this[name] = properties[name];
		}
	}

	/**
     * Change this sprite's position by the given amounts.
     * @param {number} dx - value to add to the position x coordinate
     * @param {number} dy - value to add to the position y coordinate
     */
	moveBy(dx, dy)
	{
		this.position.addValues(dx, dy);
	}	

	/**
     * Change the opacity when drawing, 
     *   enabling objects underneath to be partially visible
     *   by blending their colors with the colors of this object.
     * <br>
     * 0 = fully transparent (appears invisible);  1 = fully opaque (appears solid) 
     * @param {number} opacity - opacity of this object
     */
    setOpacity(opacity)
	{
		this.opacity = opacity;
	}

	/**
     * Set whether this sprite should be visible or not;
     *  determines whether sprite will be drawn on canvas.
     * @param {boolean} visible - determines if this sprite is visible or not
     */
    setVisible(visible)
	{
		this.visible = visible;
	}

	/**
	 * Remove this sprite from the group that contains it.
	 */
	destroy()
	{
		this.parentGroup.removeSprite(this);
	}


	/**
	 * Add an {@link Action} to this object: a special function that
	 *  will be automatically applied over time until it is complete.
	 * <br>
	 * Most common actions can be created with the static methods in the
	 * {@link ActionFactory} class.
	 * <br>
	 * All actions added to this object are performed in parallel, unless
	 *  enclosed by a {@link ActionFactory#sequence|Sequence} action.
	 * @param {Action} action - an action to be applied to this object
	 */
	addAction(action)
	{
		this.actionList.push(action);
	}

	/**
	 *  Process any {@link Action|Actions} that have been added to this Label.
     *  @param {number} deltaTime - time elapsed since previous frame
	 */
	update(deltaTime)
	{
		// Update all actions (in parallel, by default).
        // Using a copy of the list to avoid skipping the next action in the list
        //  when the previous action is removed.
		let actionListCopy = this.actionList.slice();
		for (let action of actionListCopy)
		{
			let finished = action.apply(this, deltaTime);
			if (finished)
			{
				let index = this.actionList.indexOf(action);
				if (index > -1)
					this.actionList.splice(index, 1);
			}
		}
	}

	/**
	 * Draw the label on a canvas, using the style properties specified by this object.
     * @param context - the graphics context object associated to the game canvas
	 */
    draw(context)
	{
		if ( !this.visible )
			return;

		context.font = this.fontSize + "px " + this.fontName;
		context.fillStyle = this.fontColor;
		context.textAlign = this.alignment;
		
		context.setTransform(1,0, 0,1, 0,0); 
		context.globalAlpha = this.opacity;

		// shadow draw settings
		if (this.drawShadow)
		{
			context.shadowColor = this.shadowColor;
			context.shadowBlur = this.shadowSize;
		}

		// add support for multiline text (line breaks indicated by "\n")
		let textArray  = this.text.split("\n");
		let lineSkip = this.fontSize * this.lineHeight;

		// draw filled in text (multiple times to increase drop shadow intensity)
		for (let i = 0; i < textArray.length; i++)
		{
			context.fillText( textArray[i], this.position.x, this.position.y + i * lineSkip);
			context.fillText( textArray[i], this.position.x, this.position.y + i * lineSkip);
		}

		// disable shadowBlur, otherwise all sprites drawn later will have shadows
		context.shadowBlur = 0;
		// draw filled text again, to fill over interior shadow blur, that may be a different color
		for (let i = 0; i < textArray.length; i++)
		{
			context.fillText( textArray[i], this.position.x, this.position.y + i * lineSkip);
		}

		// draw border last, so not hidden by filled text
		if (this.drawBorder)
		{
			context.strokeStyle = this.borderColor;
			context.lineWidth = this.borderSize;
			for (let i = 0; i < textArray.length; i++)
			{
				context.strokeText( textArray[i], this.position.x, this.position.y + i * lineSkip);
			}
		}		
	}

}

export { Label };