blob: a7224940a53ee78a734cf6b27cf34959b5cbce66 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2016 EclipseSource and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* EclipseSource - initial API and implementation
******************************************************************************/
rwt.qx.Class.define( "rwt.widgets.GC", {
extend : rwt.qx.Object,
construct : function( control ) {
this.base( arguments );
this._control = control;
this._control.addEventListener( "create", this._onControlCreate, this );
this._control.addEventListener( "changeWidth", this._onControlChangeWidth, this );
this._control.addEventListener( "changeHeight", this._onControlChangeHeight, this );
this._canvas = null;
this._context = null;
this._createCanvas();
this._canvas.rwtObject = this; // like "rwtWidget" in Widget.js, useful for custom JS components
if( this._control.isCreated() ) {
this._addCanvasToDOM();
}
this._linearGradient = null;
},
destruct : function() {
this._control.removeEventListener( "create", this._onControlCreate, this );
this._control.removeEventListener( "changeWidth", this._onControlChangeWidth, this );
this._control.removeEventListener( "changeHeight", this._onControlChangeHeight, this );
if( this._control.isCreated() && !this._control.isDisposed() ) {
this._removeCanvasFromDOM();
}
this._control = null;
this._canvas.rwtObject = null;
this._canvas = null;
if( this._context.dispose ) {
this._context.dispose();
}
this._context = null;
},
members : {
init : function( x, y, width, height, font, background, foreground ) {
this._initClipping( x, y, width, height );
this._initFields( font, background, foreground );
this._control.dispatchSimpleEvent( "paint" ); // client-side painting on server-side redraw
},
/**
* Executes drawing operations using the HTML5-Canvas 2D-Context syntax.
* Only a subset is supported on all browser, especially IE is limited.
* Each operation is an array starting with the name of the function to call, followed
* by its parameters. Properties are treated the same way, i.e. [ "propertyName", "value" ].
* Other differences from official HTML5-Canvas API:
* - Colors are to be given as array ( [ red, green blue ] )
* - "addColorStop" will automatically applied to the last created gradient.
* - To assign the last created linear gradient as a style, use "linearGradient" as the value.
* - strokeText behaves like fillText and fillText draws a rectangular background
* - ellipse is not a W3C standard, only WHATWG, but we need it for SWT arc to work.
*/
draw : function( operations ) {
for( var i = 0; i < operations.length; i++ ) {
try {
var op = operations[ i ][ 0 ];
switch( op ) {
case "fillStyle":
case "strokeStyle":
case "globalAlpha":
case "lineWidth":
case "lineCap":
case "lineJoin":
case "font":
this._setProperty( operations[ i ] );
break;
case "createLinearGradient":
case "addColorStop":
case "fillText":
case "strokeText":
case "ellipse":
case "drawImage":
case "setTransform":
this[ "_" + op ]( operations[ i ] );
break;
default:
this._context[ op ].apply( this._context, operations[ i ].slice( 1 ) );
break;
}
} catch( ex ) {
var opArrStr = "[ " + operations[ i ].join( ", " ) + " ]";
throw new Error( "Drawing operation failed: " + opArrStr + " :" + ex.message );
}
}
},
getNativeContext : function() {
return this._context;
},
////////////
// Internals
_createCanvas : function() {
this._canvas = document.createElement( "canvas" );
this._context = this._canvas.getContext( "2d" );
},
_onControlCreate : function() {
this._addCanvasToDOM();
},
_addCanvasToDOM : function() {
var controlElement = this._control._getTargetNode();
var firstChild = controlElement.firstChild;
if( firstChild ) {
controlElement.insertBefore( this._canvas, firstChild );
} else {
controlElement.appendChild( this._canvas );
}
},
_removeCanvasFromDOM : function() {
this._canvas.parentNode.removeChild( this._canvas );
},
_onControlChangeWidth : function( event ) {
var width = event.getValue();
this._canvas.width = width;
this._canvas.style.width = width + "px";
},
_onControlChangeHeight : function( event ) {
var height = event.getValue();
this._canvas.height = height;
this._canvas.style.height = height + "px";
},
_initClipping : function( x, y, width, height ) {
this._context.clearRect( x, y, width, height );
this._context.beginPath();
this._context.rect( x, y, width, height );
this._context.clip();
},
_initFields : function( font, background, foreground ) {
this._context.strokeStyle = rwt.util.Colors.rgbToRgbString( foreground );
this._context.fillStyle = rwt.util.Colors.rgbToRgbString( background );
this._context.globalAlpha = 1.0;
this._context.lineWidth = 1;
this._context.lineCap = "butt";
this._context.lineJoin = "miter";
this._context.font = this._toCssFont( font );
this._context.textBaseline = "top";
this._context.textAlign = "left";
},
// See http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#building-paths
_ellipse : function( operation ) {
var cx = operation[ 1 ];
var cy = operation[ 2 ];
var rx = operation[ 3 ];
var ry = operation[ 4 ];
//var rotation = operation[ 5 ]; // not supported
var startAngle = operation[ 6 ];
var endAngle = operation[ 7 ];
var dir = operation[ 8 ];
if( rx > 0 && ry > 0 ) {
this._context.translate( cx, cy );
// TODO [tb] : using scale here changes the stroke-width also, looks wrong
this._context.scale( 1, ry / rx );
this._context.arc( 0, 0, rx, startAngle, endAngle, dir );
}
},
_setProperty : function( operation ) {
var property = operation[ 0 ];
var value = operation[ 1 ];
if( value === "linearGradient" ) {
value = this._linearGradient;
} else if( property === "fillStyle" || property === "strokeStyle" ) {
value = rwt.util.Colors.rgbToRgbString( value );
} else if( property === "font" ) {
value = this._toCssFont( value );
}
this._context[ property ] = value;
},
_strokeText : function( operation ) {
var x = operation[ 5 ];
var y = operation[ 6 ];
var text = this._prepareText.apply( this, operation.slice( 1, 5 ) );
var lines = text.split( "\n" );
if( lines.length > 1 ) {
var textBounds = this._getTextBounds.apply( this, operation.slice( 1, 7 ) );
this._drawText( lines, textBounds, false );
} else {
this._context.save();
this._context.fillStyle = this._context.strokeStyle;
this._context.fillText( text, x, y );
this._context.restore();
}
},
_fillText : function( operation ) {
var text = this._prepareText.apply( this, operation.slice( 1, 5 ) );
var lines = text.split( "\n" );
var textBounds = this._getTextBounds.apply( this, operation.slice( 1, 7 ) );
this._drawText( lines, textBounds, true );
},
_drawText : function( textLines, bounds, fill ) {
this._context.save();
if( fill ) {
this._context.fillRect.apply( this._context, bounds );
}
this._context.fillStyle = this._context.strokeStyle;
var lineHeight = bounds[ 3 ] / textLines.length;
for( var i = 0; i < textLines.length; i++ ) {
this._context.fillText( textLines[ i ], bounds[ 0 ], i * lineHeight + bounds[ 1 ] );
}
this._context.restore();
},
_drawImage : function( operation ) {
var args = operation.slice( 1 );
var image = new Image();
image.src = args[ 0 ];
args[ 0 ] = image;
// On (native) canvas, only loaded images can be drawn:
if( image.complete ) {
this._context.drawImage.apply( this._context, args );
} else {
var alpha = this._context.globalAlpha;
var context = this._context;
image.onload = function() {
// TODO [tb] : The z-order will be wrong in this case.
context.save();
context.globalAlpha = alpha;
context.drawImage.apply( context, args );
context.restore();
};
}
},
_setTransform : function( operation ) {
this._context.setTransform.apply( this._context, operation.slice( 1 ) );
},
_createLinearGradient : function( operation ) {
var func = this._context.createLinearGradient;
this._linearGradient = func.apply( this._context, operation.slice( 1 ) );
},
_addColorStop : function( operation ) {
this._linearGradient.addColorStop(
operation[ 1 ],
rwt.util.Colors.rgbToRgbString( operation[ 2 ] )
);
},
_prepareText : function( value, drawMnemonic, drawDelemiter, drawTab ) {
var EncodingUtil = rwt.util.Encoding;
var text = drawMnemonic ? EncodingUtil.removeAmpersandControlCharacters( value ) : value;
var replacement = drawDelemiter ? "\n" : "";
text = EncodingUtil.replaceNewLines( text, replacement );
replacement = drawTab ? " " : "";
text = text.replace( /\t/g, replacement );
return text;
},
_getTextBounds : function( text, drawMnemonic, drawDelemiter, drawTab, x, y ) {
var escapedText = this._escapeText( text, drawMnemonic, drawDelemiter, drawTab );
var fontProps = {};
rwt.html.Font.fromString( this._context.font ).renderStyle( fontProps );
var calc = rwt.widgets.util.FontSizeCalculation;
var dimension = calc.computeTextDimensions( escapedText, fontProps );
return [ x, y, dimension[ 0 ], dimension[ 1 ] ];
},
_escapeText : function( value, drawMnemonic, drawDelemiter, drawTab ) {
var EncodingUtil = rwt.util.Encoding;
var text = EncodingUtil.escapeText( value, drawMnemonic );
var replacement = drawDelemiter ? "<br/>" : "";
text = EncodingUtil.replaceNewLines( text, replacement );
replacement = drawTab ? "&nbsp;&nbsp;&nbsp;&nbsp;" : "";
text = text.replace( /\t/g, replacement );
return text;
},
_toCssFont : function( fontArray ) {
var result = "";
if( fontArray[ 3 ] ) {
result += "italic ";
}
if( fontArray[ 2 ] ) {
result += "bold ";
}
result += fontArray[ 1 ] + "px ";
result += fontArray[ 0 ].join( "," );
return result;
}
}
} );