Created
January 28, 2014 13:30
-
-
Save ondrek/8667644 to your computer and use it in GitHub Desktop.
Canvas Implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
goog.provide( "lib.display.CanvasText" ); | |
goog.require( "lib.display.IDisplayObject" ); | |
goog.require( "lib.display.CanvasSprite" ); | |
goog.require( "goog.dom" ); | |
goog.require( "goog.events.Event" ); | |
goog.require( "goog.asserts" ); | |
goog.require( "easy.data.domain.placeholder.font.FontStyle" ); | |
goog.require( "goog.math.Coordinate" ); | |
goog.require( "easy.data.domain.Color" ); | |
/** | |
* @constructor | |
* @extends {lib.display.CanvasSprite} | |
* | |
* @param {goog.math.Coordinate} coordinates | |
* @param {number} aWidth width of this sprite | |
* @param {number} aHeight width of this sprite | |
* | |
* @param {boolean=} isWrapped | |
* @param {number=} wrappingSize | |
* @param {string=} text | |
* @param {easy.data.domain.placeholder.font.FontStyle=} fontStyle | |
* @param {easy.data.domain.Color=} fontColor | |
* @param {number=} fontSize | |
* @param {string=} font | |
* | |
*/ | |
lib.display.CanvasText = function( coordinates, aWidth, aHeight, text, isWrapped, wrappingSize, fontStyle, fontColor, fontSize, font ) | |
{ | |
goog.base( this, coordinates.x, coordinates.y, aWidth, aHeight ); | |
this._width = aWidth; | |
this._height = aHeight; | |
this._textPosX = this.getX(); | |
this._textPosY = this.getY(); | |
if (fontStyle) { | |
this.setFontStyle(fontStyle); | |
} else { | |
this.setFontStyle( new easy.data.domain.placeholder.font.FontStyle(false, false, false) ); | |
} | |
if (fontColor) { | |
this.setFontColor(fontColor); | |
} else { | |
this.setFontColor( new easy.data.domain.Color("000") ); | |
} | |
if (fontSize) { | |
this.setFontSize(fontSize); | |
} else { | |
this.setFontSize(14); | |
} | |
if (font) { | |
this.setFont(font); | |
} else { | |
this.setFont("Arial"); | |
} | |
if (text) { | |
this.setText(text); | |
} else { | |
this.setText(""); | |
} | |
this.setPadding(0); | |
this.setRotation(0); | |
// TODO : wrapping size subtract text coordinates from object width and height | |
// (i.e. text x at 10 and text y at 10 should subtract 20px from width and 20px from height) | |
// (like CSS margin/padding) | |
this._isWrapped = (isWrapped===undefined) ? true : isWrapped; | |
this.setWrappingSize( wrappingSize ? wrappingSize : aWidth ); | |
this.setLineHeight(this._fontSize*1.3); | |
// this is by default false | |
this.listenToMouseWheel = true; | |
this.listenToDoubleClick = true; | |
// ensure we can invoke multiple "handlePress"-events ;) | |
}; | |
goog.inherits( lib.display.CanvasText, lib.display.CanvasSprite ); | |
/* class constants */ | |
/** @public @type {string} @const */ lib.display.CanvasText.TEXTBASELINE_TOP = "top"; | |
/** @public @type {string} @const */ lib.display.CanvasText.TEXTBASELINE_HANGING = "hanging"; | |
/** @public @type {string} @const */ lib.display.CanvasText.TEXTBASELINE_MIDDLE = "middle"; | |
/** @public @type {string} @const */ lib.display.CanvasText.TEXTBASELINE_ALPHABETIC = "alphabetic"; | |
/** @public @type {string} @const */ lib.display.CanvasText.TEXTBASELINE_IDEOGRAPHIC = "ideographic"; | |
/** @public @type {string} @const */ lib.display.CanvasText.TEXTBASELINE_BOTTOM = "bottom"; | |
/** @public @type {string} @const */ lib.display.CanvasText.TEXTALIGN_START = "start"; | |
/** @public @type {string} @const */ lib.display.CanvasText.TEXTALIGN_END = "end"; | |
/** @public @type {string} @const */ lib.display.CanvasText.TEXTALIGN_LEFT = "left"; | |
/** @public @type {string} @const */ lib.display.CanvasText.TEXTALIGN_CENTER = "center"; | |
/** @public @type {string} @const */ lib.display.CanvasText.TEXTALIGN_RIGHT = "right"; | |
/* private variables */ | |
/** @private @type {number} */ lib.display.CanvasText.prototype._lastClickTime; | |
/** @private @type {number} */ lib.display.CanvasText.prototype._inputContent; | |
/** @private @type {string} */ lib.display.CanvasText.prototype._rotation; | |
/** @private @type {string} */ lib.display.CanvasText.prototype._wrappedText; | |
/** @private @type {string} */ lib.display.CanvasText.prototype._padding; | |
/** @private @type {CanvasRenderingContext2D} */ lib.display.CanvasText.prototype._ctx; | |
/** @private @type {string} */ lib.display.CanvasText.prototype._ctxFont; | |
/** @private @type {string} */ lib.display.CanvasText.prototype._font; | |
/** @private @type {easy.data.domain.placeholder.font.FontStyle} */ lib.display.CanvasText.prototype._fontStyle; | |
/** @private @type {number} */ lib.display.CanvasText.prototype._fontSize; | |
/** @private @type {easy.data.domain.Color} */ lib.display.CanvasText.prototype._fontColor; | |
/** @private @type {number} */ lib.display.CanvasText.prototype._lineHeight; | |
/** @private @type {string} */ lib.display.CanvasText.prototype._text; | |
/** @private @type {boolean} */ lib.display.CanvasText.prototype._isWrapped; | |
/** @private @type {boolean} */ lib.display.CanvasText.prototype._isEditing = false; | |
/** @private @type {number} */ lib.display.CanvasText.prototype._textPosX; | |
/** @private @type {number} */ lib.display.CanvasText.prototype._textPosY; | |
/** @private @type {number} */ lib.display.CanvasText.prototype._width; | |
/** @private @type {number} */ lib.display.CanvasText.prototype._height; | |
/** @private @type {string} */ lib.display.CanvasText.prototype._textAlign = lib.display.CanvasText.TEXTALIGN_START; | |
/** @private @type {string} */ lib.display.CanvasText.prototype._textBaseLine = lib.display.CanvasText.TEXTBASELINE_ALPHABETIC; | |
/** @private @type {number} */ lib.display.CanvasText.prototype._wrappingSize = 500; | |
/** @private @type {lib.display.Sprite} */ lib.display.CanvasText.prototype._input; | |
/* public methods */ | |
/** | |
* @override | |
* @public | |
* | |
* @param {CanvasRenderingContext2D} aCanvasContext | |
*/ | |
lib.display.CanvasText.prototype.draw = function( aCanvasContext ) | |
{ | |
// aCanvasContext.save(); // save currently rendered canvas content | |
// aCanvasContext.rotate( this.getRotation() ); // rotate to match text angle | |
// TODO : text should be rotated from the center, this translation should be at center of this Object ;) | |
// aCanvasContext.translate( this._bounds.width / 2, this._bounds.height / 2 ); | |
// actual text rendering | |
aCanvasContext.fillStyle = this._fontColor.getHexValue(); | |
aCanvasContext.font = this._ctxFont; | |
aCanvasContext.textBaseline = this._textBaseLine; | |
aCanvasContext.textAlign = this._textAlign; | |
this._drawText( aCanvasContext ); | |
this._drawMask( aCanvasContext ); | |
this._setBorder( aCanvasContext ); | |
// restore context to previous state | |
// aCanvasContext.restore(); | |
// TODO: should be inside rotated context ;) | |
// this._setBorder( aCanvasContext ); | |
}; | |
/** | |
* @public | |
*/ | |
lib.display.CanvasText.prototype.createInput = function() | |
{ | |
if ( goog.DEBUG ) | |
{ | |
goog.asserts.assert( this.stage, | |
"cannot create Input for CanvasText, it is not added to a StageCanvas" ); | |
goog.asserts.assert( this.stage.getElement().parentNode, | |
"cannot create Input for CanvasText, its StageCanvas is not in the DOM yet" ); | |
} | |
var parent = /** @type {Element} */ ( this.stage.getElement().parentNode ); | |
if ( !this._input ) | |
{ | |
var input = this._input = new lib.display.Sprite( goog.dom.TagName.TEXTAREA ); | |
input.addClass( "click-handler" ); | |
// input.addEventListener( goog.events.EventType.KEYDOWN, goog.bind( this.handleTyping, this )); | |
input.addEventListener( goog.events.EventType.KEYUP , goog.bind( this.handleTyping, this )); | |
} | |
// after doubleclick on stage canvas run displayTextarea | |
// NO, StageCanvas has multiple children, adding a listener to a parent that only applies | |
// to a single child, is bad practice, override "handleInteraction"-method instead! | |
// this.stage.addEventListener( goog.events.EventType.CLICK , goog.bind( this.displayTextarea, this )); | |
// stupid way to align initial offsets ;) | |
this.setX( this.getX() ); | |
this.setY( this.getY() ); | |
parent.appendChild( this._input.getElement() ); | |
}; | |
/** | |
* @public | |
*/ | |
lib.display.CanvasText.prototype.handleTyping = function( aEvent ) | |
{ | |
this.setText( this._input.getElement().value); | |
}; | |
/** | |
* @public | |
*/ | |
lib.display.CanvasText.prototype.displayTextarea = function( aEvent ) | |
{ | |
console.log("hello", aEvent); | |
}; | |
/** | |
* @public | |
* @param {CanvasRenderingContext2D} aCanvasContext | |
*/ | |
lib.display.CanvasText.prototype._setBorder = function( aCanvasContext ) | |
{ | |
// http://diveintohtml5.info/canvas.html#paths | |
// scroll to ASK PROFESSOR MARKUP: Why did you start x and y at 0.5? Why not 0?; | |
var movedX = this.getX() - 0.5 - this._padding/2; | |
var movedY = this.getY() - 0.5 - this._padding/2; | |
aCanvasContext.beginPath(); | |
aCanvasContext.moveTo( movedX, movedY ); | |
aCanvasContext.lineTo( movedX, movedY + this._height ); | |
aCanvasContext.lineTo( movedX + this._width, movedY + this._height ); | |
aCanvasContext.lineTo( movedX + this._width, movedY ); | |
aCanvasContext.lineTo( movedX, movedY); | |
aCanvasContext.lineWidth = 1; | |
aCanvasContext.strokeStyle = '#f00'; | |
aCanvasContext.stroke(); | |
}; | |
/** | |
* @public | |
* @param {CanvasRenderingContext2D} aCanvasContext | |
*/ | |
lib.display.CanvasText.prototype._drawMask = function( aCanvasContext ) | |
{ | |
aCanvasContext.globalCompositeOperation = 'destination-in'; | |
// +/- 1 because we want display also out-of-bounce border | |
aCanvasContext.fillRect( | |
this.getX() - 1,// - this._padding/2, | |
this.getY() - 1,// - this._padding/2, | |
this._width + 1, | |
this._height + 1 | |
); | |
aCanvasContext.globalCompositeOperation = 'source-over'; | |
}; | |
/** | |
* @public | |
* @param {string} newText | |
* @param {boolean=} isWrapped | |
* @param {number=} wrapSize | |
*/ | |
lib.display.CanvasText.prototype.setText = function( newText, isWrapped, wrapSize ) | |
{ | |
this._text = newText; | |
}; | |
/** | |
* @public | |
* @return {string} | |
*/ | |
lib.display.CanvasText.prototype.getText = function() | |
{ | |
return this._text; | |
}; | |
/** | |
* @public | |
*/ | |
lib.display.CanvasText.prototype.clearText = function() | |
{ | |
this._text = ""; | |
}; | |
/** | |
* @public | |
* @return {boolean} | |
*/ | |
lib.display.CanvasText.prototype.hasText = function() | |
{ | |
return (this._text.length>0); | |
}; | |
/** | |
* @public | |
* @param {easy.data.domain.placeholder.font.FontStyle} fontStyle | |
*/ | |
lib.display.CanvasText.prototype.setFontStyle = function( fontStyle ) | |
{ | |
// todo implement own underline | |
var newFontStyle = ""; | |
if (!fontStyle.italic && !fontStyle.bold && !fontStyle.underline) { | |
newFontStyle += "normal"; | |
} | |
if (fontStyle.bold) { | |
newFontStyle += " bold"; | |
} | |
if (fontStyle.italic) { | |
newFontStyle += " italic"; | |
} | |
if (fontStyle.underline) { | |
// newFontStyle += " underline"; | |
} | |
this._fontStyle = newFontStyle; | |
this._ctxFont = this._fontStyle + " " + this._fontSize + "px " + this._font; | |
}; | |
/** | |
* @private | |
* | |
lib.display.CanvasText.prototype._underlineText = function(context, x, y) | |
{ | |
// this should be commented now, it's broken | |
// new version in CanvasText-2.js | |
var textUnderline = function(context,text,x,y,color,textSize,align){ | |
var textWidth = context.measureText(text).width; | |
var startX = 0; | |
var startY = y + (parseInt(textSize)/15); | |
var endX = 0; | |
var endY = startY; | |
var underlineHeight = parseInt(textSize)/15; | |
if (underlineHeight < 1){ | |
underlineHeight = 1; | |
} | |
context.beginPath(); | |
if (align == "center"){ | |
startX = x - (textWidth/2); | |
endX = x + (textWidth/2); | |
} else if (align == "right"){ | |
startX = x-textWidth; | |
endX = x; | |
} else { | |
startX = x; | |
endX = x + textWidth; | |
} | |
context.strokeStyle = color; | |
context.lineWidth = underlineHeight; | |
context.moveTo(startX,startY); | |
context.lineTo(endX,endY);context.strokeStyle = "blue"; | |
context.stroke(); | |
}; | |
// Initialize the variables with the required data | |
var text = "Hello Igor!!"; | |
var textAlign = "center"; | |
var textColor = "blue"; | |
var fontSize = "15px"; | |
var fontFamily = "Calibri"; | |
// Set the canvas context properties | |
context.font = fontSize + " " + fontFamily; | |
context.textAlign = textAlign; | |
context.fillStyle = textColor; | |
// Display the text on canvas | |
context.fillText( text, x, y ); | |
// Call the function to underline the text | |
// We need to pass some values to our function so that it can perform the necessary calculations. | |
textUnderline( context, text, x, y, textColor, fontSize, textAlign ); | |
}; | |
*/ | |
/** | |
* @public | |
* @return {string} | |
*/ | |
lib.display.CanvasText.prototype.getFontStyle = function() | |
{ | |
return this._fontStyle; | |
}; | |
/** | |
* @public | |
* @param {string} font | |
*/ | |
lib.display.CanvasText.prototype.setFont = function( font ) | |
{ | |
this._font = font; | |
this._ctxFont = this._fontStyle + " " + this._fontSize + "px " + this._font; | |
}; | |
/** | |
* @public | |
* @return {string} | |
*/ | |
lib.display.CanvasText.prototype.getFont = function() | |
{ | |
return this._font; | |
}; | |
/** | |
* @public | |
* @param {easy.data.domain.Color} color | |
*/ | |
lib.display.CanvasText.prototype.setFontColor = function( color ) | |
{ | |
this._fontColor = color; | |
}; | |
/** | |
* @public | |
* @return {easy.data.domain.Color} | |
*/ | |
lib.display.CanvasText.prototype.getFontColor = function() | |
{ | |
return this._fontColor; | |
}; | |
/** | |
* @public | |
* @param {number} lineHeight | |
*/ | |
lib.display.CanvasText.prototype.setLineHeight = function( lineHeight ) | |
{ | |
this._lineHeight = lineHeight; | |
}; | |
/** | |
* @public | |
* @return {number} | |
*/ | |
lib.display.CanvasText.prototype.getLineHeight = function() | |
{ | |
return this._lineHeight; | |
}; | |
/** | |
* @public | |
* @param {number} fontSize | |
*/ | |
lib.display.CanvasText.prototype.setFontSize = function( fontSize ) | |
{ | |
this._fontSize = fontSize; | |
this._ctxFont = this._fontStyle + " " + this._fontSize + "px " + this._font; | |
}; | |
/** | |
* @public | |
* @return {number} | |
*/ | |
lib.display.CanvasText.prototype.getFontSize = function() | |
{ | |
return this._fontSize; | |
}; | |
/** | |
* @public | |
* @param {number} degrees | |
*/ | |
lib.display.CanvasText.prototype.setRotation = function( degrees ) | |
{ | |
this._rotation = (Math.PI / 180) * degrees; | |
}; | |
/** | |
* @public | |
* @return {number} | |
*/ | |
lib.display.CanvasText.prototype.getRotation = function() | |
{ | |
return this._rotation; | |
}; | |
/** | |
* @public | |
* @param {number} wrappingSize | |
*/ | |
lib.display.CanvasText.prototype.setWrappingSize = function( wrappingSize ) | |
{ | |
this._wrappingSize = wrappingSize; | |
}; | |
/** | |
* @public | |
* @return {number} | |
*/ | |
lib.display.CanvasText.prototype.getWrappingSize = function() | |
{ | |
return this._wrappingSize; | |
}; | |
/** | |
* @public | |
* @param {number} padding | |
*/ | |
lib.display.CanvasText.prototype.setPadding = function( padding ) | |
{ | |
this._padding = padding; | |
//this.setX( this.getX()+(this._padding/2) ); | |
//this.setY( this.getY()+(this._padding/2) ); | |
//this._wrappingSize = this._wrappingSize - this._padding; | |
}; | |
/** | |
* @public | |
* @return {number} | |
*/ | |
lib.display.CanvasText.prototype.getPadding = function() | |
{ | |
return this._padding; | |
}; | |
/** | |
* @override | |
* @public | |
* @param {number} aValue | |
lib.display.CanvasText.prototype.setX = function( aValue ) | |
{ | |
goog.base( this, "setY", aValue ); // super call | |
if ( this._input ) { | |
this._input.setStyle( "left", aValue + "px" ); | |
} | |
};*/ | |
/** | |
* @override | |
* @public | |
* @param {number} aValue | |
lib.display.CanvasText.prototype.setY = function( aValue ) | |
{ | |
goog.base( this, "setY", aValue ); // super call | |
if ( this._input ) { | |
this._input.setStyle( "top", aValue + "px"); | |
} | |
};*/ | |
/** | |
* @public | |
* @param {string} alignment | |
*/ | |
lib.display.CanvasText.prototype.setTextAlign = function( alignment ) | |
{ | |
( goog.DEBUG ) && goog.asserts.assert( | |
[ | |
lib.display.CanvasText.TEXTALIGN_START, | |
lib.display.CanvasText.TEXTALIGN_END, | |
lib.display.CanvasText.TEXTALIGN_LEFT, | |
lib.display.CanvasText.TEXTALIGN_CENTER, | |
lib.display.CanvasText.TEXTALIGN_RIGHT | |
].indexOf( alignment ) > -1, | |
"Sorry, but a alignment on setTextAlign must have valid HTML5 value" | |
); | |
this._textAlign = alignment; | |
}; | |
/** | |
* @public | |
* @return {string} | |
*/ | |
lib.display.CanvasText.prototype.getTextAlign = function() | |
{ | |
return this._textAlign; | |
}; | |
/** | |
* @public | |
* @param {string} textBaseLine | |
*/ | |
lib.display.CanvasText.prototype.setTextBaseline = function( textBaseLine ) | |
{ | |
( goog.DEBUG ) && goog.asserts.assert( | |
[ | |
lib.display.CanvasText.TEXTBASELINE_TOP, | |
lib.display.CanvasText.TEXTBASELINE_HANGING, | |
lib.display.CanvasText.TEXTBASELINE_MIDDLE, | |
lib.display.CanvasText.TEXTBASELINE_ALPHABETIC, | |
lib.display.CanvasText.TEXTBASELINE_IDEOGRAPHIC, | |
lib.display.CanvasText.TEXTBASELINE_BOTTOM | |
].indexOf( textBaseLine ) > -1, | |
"Sorry, but a baseline on setTextBaseline must have valid HTML5 value" | |
); | |
this._textBaseLine = textBaseLine; | |
}; | |
/** | |
* @public | |
* @return {string} | |
*/ | |
lib.display.CanvasText.prototype.getTextBaseline = function() | |
{ | |
return this._textBaseLine; | |
}; | |
/** | |
* @private | |
* @param {CanvasRenderingContext2D} aCanvasContext | |
*/ | |
lib.display.CanvasText.prototype._drawText = function( aCanvasContext ) | |
{ | |
window.linepositions = []; | |
window.datas = []; | |
var lineHeight = this.getLineHeight(); | |
var fontSize = this.getFontSize(); | |
var textPositionX = this.getX() + this._textPosX; | |
var textPositionY = this.getY() + this._textPosY; | |
if (this._isWrapped) { | |
this._wrappedText = this.wordWrappedText( aCanvasContext ); | |
this._wrappedText.forEach(function(row, i){ | |
window.linepositions.push( i*lineHeight+fontSize+textPositionY + " > " + row ); | |
window.datas.push( { i : i, lineHeight : lineHeight, fontSize: fontSize, textPositionY : textPositionY } ); | |
aCanvasContext.fillText(row, textPositionX, i*lineHeight+fontSize+textPositionY); | |
}); | |
} else { | |
aCanvasContext.fillText(this._text, textPositionX, textPositionY+lineHeight+fontSize); | |
} | |
}; | |
/** | |
* Returns an array of strings representing the text with line-breaks as it should | |
* be rendered considering the available width and the font rendering settings on the canvas object. | |
* Each entry in the array is a final string that should be rendered. This is necessary since there | |
* is no automatic word-wrapping for text rendering methods on canvas. | |
* | |
* @private | |
* | |
* @param {CanvasRenderingContext2D} aCanvasContext | |
* | |
* @return {Array} | |
*/ | |
lib.display.CanvasText.prototype.wordWrappedText = function( aCanvasContext ) | |
{ | |
var maxWidth = this._wrappingSize; | |
var EXPLICIT_NEW_LINE = {}; // Dummy object to represent line breaks | |
var nextBlock; | |
var evaluatedWidth = 0; | |
var blocks = _getWordBlocks(this._text); | |
function _getWordBlocks( originalText ){ | |
// each word is one block | |
var allWords = []; | |
// Break down the original string into blocks by explicit line breaks first, as they take precedence | |
var EXPLICITLINE_REGEXP = /[\n]/; | |
var newLineBlocks = originalText.split(EXPLICITLINE_REGEXP); | |
// every newLineBlocks cut into separated words | |
while (newLineBlocks.length > 0) | |
{ | |
var processedBlock = newLineBlocks.shift(); | |
var WHITESPACE_REGEXP = /[\s]/; | |
var splitProcessedBlock = processedBlock.split(WHITESPACE_REGEXP); | |
while (splitProcessedBlock.length > 0) | |
{ | |
allWords.push(splitProcessedBlock.shift()); | |
} | |
allWords.push(EXPLICIT_NEW_LINE); | |
} | |
return allWords; | |
} | |
// By this point we will have an array of single words (by white-space) and explicitly placed line breaks. | |
// Now we can start assessing the non-explicitly broken sequences of words by length and adding more line-break markers as needed. | |
// Start the assessment by beginning the evaluation with the first entry in the list | |
var portion = blocks.shift(); | |
var result = []; | |
while (blocks.length > 0) | |
{ | |
evaluatedWidth = aCanvasContext.measureText(portion); | |
if (evaluatedWidth.width > maxWidth) // only if width is smaller than word length | |
{ | |
// Break by letters before proceeding further | |
var letterBreaks = this.breakByLettersIfNecessary(aCanvasContext, portion, maxWidth); | |
while (letterBreaks.length > 0) | |
{ | |
portion = letterBreaks.shift(); | |
if (letterBreaks.length > 0) result.push(portion); | |
} | |
} | |
nextBlock = blocks.shift(); | |
var skipLine = false; | |
if (nextBlock == EXPLICIT_NEW_LINE) | |
{ | |
skipLine = true; | |
} | |
else | |
{ | |
// Measure the upcoming text line | |
evaluatedWidth = aCanvasContext.measureText(portion + " " + nextBlock); | |
if (evaluatedWidth.width > maxWidth) | |
{ | |
skipLine = true; | |
} | |
} | |
if (skipLine) | |
{ | |
// Text line was deemed to be implicitly broken | |
// Add the text we've gathered so far as a valid line | |
if (portion != "" || nextBlock == EXPLICIT_NEW_LINE) | |
{ | |
result.push(portion); | |
} | |
// If there was no explicit line break, the part of text that was not eventually added to the line becomes the beginning of a new line | |
if (nextBlock != EXPLICIT_NEW_LINE) | |
{ | |
portion = nextBlock; | |
} | |
else | |
{ | |
// If text was explicitly broken, the new line begins as empty | |
portion = ""; | |
} | |
} | |
else | |
{ | |
// Text can continue, add the assessed portion to the line compilation and continue | |
portion += ((portion == "" ? "" : " ") + nextBlock); | |
} | |
} | |
// Add the remainder as the last entry, if it's a valid one. Usually an empty string is a result of splitting | |
// a string when the result is just a single entry; an empty string is added as a second array entry in this case. | |
if (portion != "") | |
{ | |
result.push(portion); | |
} | |
return result; | |
}; | |
/** | |
* Check the size of the text as solid text line and break it by letter if necessary. | |
* If a single-entry array is returned, there was no letter break. | |
* | |
* @param {CanvasRenderingContext2D} aCanvasContext | |
* @param {string} text | |
* @param {number} maxWidth | |
* | |
* @return {Array} | |
*/ | |
lib.display.CanvasText.prototype.breakByLettersIfNecessary = function ( aCanvasContext, text, maxWidth ) | |
{ | |
var result = []; | |
// Measure the existing line - it can be too long already | |
var evaluatedWidth = aCanvasContext.measureText(text); | |
if (evaluatedWidth.width > maxWidth) | |
{ | |
// The text so far is already too long, break by letters | |
var brokenByLetter = []; | |
var currentLine = ""; | |
for (var i = 0; i < text.length - 1; i++) | |
{ | |
if (currentLine.length == 0) currentLine = text.charAt(i); | |
evaluatedWidth = aCanvasContext.measureText(currentLine + text.charAt(i + 1)); | |
if (evaluatedWidth.width > maxWidth) | |
{ | |
brokenByLetter.push(currentLine); | |
currentLine = text.charAt(i + 1); | |
} | |
else | |
{ | |
currentLine += text.charAt(i + 1); | |
} | |
} | |
// Add the remaining portion as the last item, which should be picked for evaluation later | |
brokenByLetter.push(currentLine); | |
// Integrate the letter-broken parts into the result | |
while (brokenByLetter.length > 0) | |
{ | |
result.push(brokenByLetter.shift()); | |
} | |
} | |
else | |
{ | |
result.push(text); | |
} | |
return result; | |
}; | |
/* protected methods */ | |
/** | |
* detect clicked character | |
* | |
* @override | |
* | |
* @param {number} aXPosition | |
* @param {number} aYPosition | |
*/ | |
lib.display.CanvasText.prototype.detectClickedCharacter = function( aXPosition, aYPosition ) | |
{ | |
// var lineNumber = ( aYPosition / this.getLineHeight() ) + this.getFontSize(); | |
// lineNumber = Math.round(lineNumber); | |
var lineNumber = ( aYPosition / this.getLineHeight() ) - this.getY() + this._textPosY;// + this._fontSize; | |
//lineNumber -= 13; | |
lineNumber = Math.ceil( lineNumber ); | |
// i * this.getLineHeight() + this.getX() + this._textPosX + this._fontSize | |
console.log( "aYPosition", aYPosition, "this.getLineHeight()", this.getLineHeight(), "this.getFontSize()", this.getFontSize() ); | |
console.log( "lineNumber ", lineNumber ); | |
}; | |
/** | |
* press handler, invoked by the "handleInteraction"-method | |
* this method will delegate drag and click logic | |
* | |
* @override | |
* @protected | |
* | |
* @param {number} aXPosition | |
* @param {number} aYPosition | |
*/ | |
lib.display.CanvasText.prototype.handlePress = function( aXPosition, aYPosition ) | |
{ | |
this.detectClickedCharacter( aXPosition, aYPosition ); | |
if ( this._lastClickTime ) { | |
// if not dragging && less < 300ms && alredy first clicked | |
var isLessThan300ms = ( +new Date() - this._lastClickTime ) < 300; | |
if ( isLessThan300ms ) { | |
// set state to true - used in disabling dragging, interaction, etc.. | |
this._isEditing = true; | |
// run editing mode, display text area and place text cursor | |
this.editingMode(true); | |
} | |
// again reset double-click functionality to prevent a triple-click | |
this._lastClickTime = null; | |
} | |
else | |
{ | |
// no timed boolean set > | |
// create timeout for timedboolean (which should be set to false when timeout passes) | |
this._lastClickTime = +new Date(); | |
} | |
// do default behaviour | |
goog.base( this, "handlePress", aXPosition, aYPosition ); | |
}; | |
/** | |
* run this function after double clicks | |
* | |
* @public | |
* | |
* @param {boolean} status | |
* | |
*/ | |
lib.display.CanvasText.prototype.editingMode = function( status ) | |
{ | |
// TODO | |
// display textarea with css class | |
// detect cursor position | |
// put a text cursor on correct position | |
if (status===true) { | |
console.log( ">> logging: true " ); | |
// add class, in css is defined display:none to display:block | |
this._input.addClass( "visible" ); | |
// set content of textare before editing on demain | |
this._input.getElement().value = this.getText(); | |
// focus textarea, doesnt work on mobile devices where needs to be extended functionality | |
this._input.getElement().focus(); | |
// listen on click and after to second click | |
this._input.addEventListener( goog.events.EventType.KEYUP, goog.bind( function(e){ | |
// is ESC pressed, text editing should be finished | |
if (e.keyCode===27) { | |
this._isEditing = false; | |
this.editingMode(false); | |
} | |
}, this )); | |
} else { | |
console.log( ">> logging: off " ); | |
this._input.removeClass( "visible" ); | |
// set to inner content new value on escape | |
this.setText( this._input.getElement().value ); | |
} | |
}; | |
/** | |
* move handler, invoked by the "handleInteraction"-method | |
* this method will delegate drag logic | |
* | |
* @override | |
* @protected | |
* | |
* @param {number} aXPosition | |
* @param {number} aYPosition | |
*/ | |
lib.display.CanvasText.prototype.handleMove = function( aXPosition, aYPosition ) | |
{ | |
// when we are dragging a ctx, we don't want to care about doubleclick | |
this._lastClickTime = null; | |
if ( !this._isEditing ) { | |
// not editing ? use default behaviour | |
goog.base( this, "handleMove", aXPosition, aYPosition ); | |
} | |
}; | |
/** | |
* | |
* as text has both Object bounds and wrapped text bounds | |
* we override the default mouse wheel handler! =o !! | |
* | |
* in other words the restriction of the wheel movement | |
* comes from the Object bounds, not some parent constraint ! | |
* | |
* @override | |
* @protected | |
* @param {goog.events.Event} aEvent | |
*/ | |
lib.display.CanvasText.prototype.handleWheelScroll = function( aEvent ) | |
{ | |
var browserEvent = aEvent.getBrowserEvent(); | |
var delta = 0; | |
var multiplier = 0.3; // lower the speed ;) | |
// positive number is moving up, negative number is moving down | |
if ( browserEvent.wheelDelta ) { | |
delta = browserEvent.wheelDelta; | |
} | |
else if ( browserEvent.detail ) { | |
delta = browserEvent.detail * -120; // Opera / FireFox | |
} | |
var targetY = this._textPosY + ( delta * multiplier ); | |
// keep within bounds | |
var sizeOfText = this._wrappedText.length * (this.getLineHeight()); | |
if ( targetY > this._bounds.top ) { | |
// targetY = this._bounds.top; | |
return; | |
} | |
else if ( targetY <= -( sizeOfText - this._bounds.height )) | |
{ | |
return; | |
//targetY = this._bounds.top + this._bounds.height; | |
} else if (targetY > 10 ) { | |
// console.log("bitch!"); | |
return; | |
} | |
this._textPosY = targetY; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment