| /******************************************************************************* |
| * 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 ? " " : ""; |
| 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; |
| } |
| |
| } |
| } ); |