js/Mobilizing/text/TextField.js
import * as _Math from "../core/util/Math";
import Font from "./Font";
import { Raleway } from "../misc/DefaultFonts";
import EventEmitter from "../core/util/EventEmitter";
import StyledLetter from "./StyledLetter";
/**
* Triggered when the canvas has been redrawn, useful to synchronise Texture update and canvas refresh
* @event drawn
*/
const EVT_DRAWN = "drawn";
export default class TextField {
/**
* TextField to simulate the input textfield we can find in the DOM.
* This is implemented in an HTLM Canvas so it can be rendered in a texture.
*
* @param {Object} params Parameters object, given by the constructor.
* @param {Number} [params.maxCharCount = 1000]
* @param {Number} [params.size = 20]
* @param {CSSColor} [params.color = "gray"]
* @param {CSSColor} [params.backgroundColor = "white"]
* @param {Number} [params.margins = 10]
* @param {Number} [params.width = 300]
* @param {Number} [params.height = 100]
* @param {Font} [params.font = undefined]
* @param {Font} [params.fontItalic = undefined]
* @param {Font} [params.fontBold = undefined]
* @param {Font} [params.fontBoldItalic = undefined]
* @param {CSSColor} [params.cursorColor = "gray"]
* @param {Number} [params.cursorWidth = 2]
* @param {Number} [params.blinkTime = 300]
*
* @example
* //TODO
*/
constructor({
maxCharCount = 1000,
size = 20,
color = "gray",
backgroundColor = "white",
margins = 10,
width = 300,
height = 100,
font = undefined,
fontItalic = undefined,
fontBold = undefined,
fontBoldItalic = undefined,
cursorColor = "gray",
cursorWidth = 2,
blinkTime = 300,
} = {}) {
this.maxCharCount = maxCharCount;
this.size = size;
this.color = color;
this.backgroundColor = backgroundColor;
this.margins = margins;
this.width = width;
this.height = height;
this.font = font;
this.fontItalic = fontItalic;
this.fontBold = fontBold;
this.fontBoldItalic = fontBoldItalic;
this.cursorColor = cursorColor;
this.cursorWidth = cursorWidth;
this.blinkTime = blinkTime;
this._ready = false;
this._styledLetters = [];
this._text = "";
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";
this.setup();
this._blinkState = false;
setInterval(this.onBlink.bind(this), this.blinkTime);
}
/**
* Setup bloc called after default font loading
* @private
*/
setup() {
this._font = (this.font ? this.font : new Font({ base64String: Raleway.regular }));
this._italicFont = (this.fontItalic ? this.fontItalic : new Font({ base64String: Raleway.italic }));
this._boldFont = (this.fontBold ? this.fontBold : new Font({ base64String: Raleway.bold }));
this._boldItalicFont = (this.fontBoldItalic ? this.fontBoldItalic : new Font({ base64String: Raleway.boldItalic }));
this._currentFont = this._font;
this.render();
}
/**
* Set the font.
* @param {Font} font Mobilizing font to use for the next letter
*/
setFont(font) {
this._currentFont = font;
}
/**
* Set the size of the next letter
* @param {Number} size the new size of the font
*/
setSize(size) {
this.size = size;
}
/**
* Set the color of the next letter
* @param {Color} color the new Mobilizing Color
*/
setColor(color) {
this.color = color;
}
/**
* add a letter to the field content. Styles (font, color, etc) should be defined before calling this method.
* @param {String} value the letter to add
*/
addLetter(value) {
const char = value.charCodeAt(0);
let letter = String.fromCharCode(char);
this._text += letter;
//manage new line feed
if (value === "\n") {
letter = value;
}
const styledLetter = new StyledLetter({
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)
*/
delete() {
if (this._cursorIndex >= 0) {
this._styledLetters.splice(this._cursorIndex, 1);
//console.log("delete",this._cursorIndex,this._styledLetters);
this.moveCursorBack();
}
}
/**
* Clears this textField : erase everything and make the cursor back to the first character place.
*/
clear(){
this._cursorIndex = 0;
this._styledLetters = [];
this._text = "";
}
/**
* Cursor blink callback
* @private
*/
onBlink() {
this._blinkState = !this._blinkState;
//console.log("blink",this._blinkState);
this.render();
}
/**
* Moves the cursor to the next letter
*/
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
*/
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
* @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
* @param {Number} x
* @param {Number} y
*/
pickLetter(x, y) {
for (let i = 0; i < this._styledLetters.length; i++) {
const el = this._styledLetters[i];
const bbox = el.path.getBoundingBox();
const 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
*/
render() {
//background color
this._canvasContext.fillStyle = this.backgroundColor;
this._canvasContext.fillRect(0, 0, this.width, this.height);
//x position of drawing (letter pos in x)
let letterXOffset = this.margins;
//y position of drawing (letter pos in y, or baseline)
let lineYOffset = this.margins;
for (let i = 0; i < this._styledLetters.length; i++) {
if (i < this.maxCharCount) {
const 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
const tempWidth = letterXOffset + el.width;
if (tempWidth > this.width - this.margins) {
lineYOffset += el.size;
letterXOffset = this.margins;
}
//test and manage special case for new line feed
if (el.letter === "\n") {
lineYOffset += el.size;
el.height = 0;
el.width = 0;
letterXOffset = this.margins;
el.setX(letterXOffset);
el.setY(lineYOffset);
}
else {
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) {
const el = this._styledLetters[this._cursorIndex];
if (el) {
const boundingBox = el.path.getBoundingBox();
this._canvasContext.beginPath();
//manage blank space
if (el.height < 1) {
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);
}
//use path boundingBox
else {
this._canvasContext.moveTo(boundingBox.x2, boundingBox.y1);
this._canvasContext.lineTo(boundingBox.x2, boundingBox.y1);
this._canvasContext.lineTo(boundingBox.x2, boundingBox.y2);
}
}
//we don't have a letter, it's the start of the text
else {
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;
this._canvasContext.stroke();
}
//emit a custom event on the canvas to refresh texture when used
this._canvas.events.trigger(EVT_DRAWN);
}
/**
* Get canvas width
* @return {Number} canvas width
*/
getWidth() {
return this.width;
}
/**
* Get canvas height
* @return {Number} canvas height
*/
getHeight() {
return this.height;
}
/**
* Get the canvas
* @return {Canvas} canvas
*/
getCanvas() {
return this._canvas;
}
/**
* Get the text displayed as a string
* @return {String} the text displayed in this TextField
*/
getText(){
return this._text;
}
}