/******************************************************************************* * 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 ******************************************************************************/ package org.eclipse.swt.internal.widgets.canvaskit; import static org.eclipse.rap.rwt.internal.lifecycle.WidgetUtil.getId; import static org.eclipse.rap.rwt.internal.protocol.RemoteObjectFactory.getRemoteObject; import static org.eclipse.rap.rwt.remote.JsonMapping.toJson; import org.eclipse.rap.json.JsonArray; import org.eclipse.rap.json.JsonObject; import org.eclipse.rap.json.JsonValue; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.internal.graphics.GCAdapter; import org.eclipse.swt.internal.graphics.GCOperation; import org.eclipse.swt.internal.graphics.GCOperation.DrawArc; import org.eclipse.swt.internal.graphics.GCOperation.DrawImage; import org.eclipse.swt.internal.graphics.GCOperation.DrawLine; import org.eclipse.swt.internal.graphics.GCOperation.DrawPath; import org.eclipse.swt.internal.graphics.GCOperation.DrawPoint; import org.eclipse.swt.internal.graphics.GCOperation.DrawPolyline; import org.eclipse.swt.internal.graphics.GCOperation.DrawRectangle; import org.eclipse.swt.internal.graphics.GCOperation.DrawRoundRectangle; import org.eclipse.swt.internal.graphics.GCOperation.DrawText; import org.eclipse.swt.internal.graphics.GCOperation.FillGradientRectangle; import org.eclipse.swt.internal.graphics.GCOperation.SetClipping; import org.eclipse.swt.internal.graphics.GCOperation.SetProperty; import org.eclipse.swt.internal.graphics.GCOperation.SetTransform; import org.eclipse.swt.internal.graphics.ImageFactory; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Widget; final class GCOperationWriter { private final Control control; private boolean initialized; private JsonArray operations; private int lineWidth; private RGB foreground; private RGB background; GCOperationWriter( Control control ) { this.control = control; } void initialize() { if( !initialized ) { lineWidth = 1; foreground = control.getForeground().getRGB(); background = control.getBackground().getRGB(); Rectangle paintRect = getPaintRect(); JsonObject parameters = new JsonObject() .add( "x", paintRect.x ) .add( "y", paintRect.y ) .add( "width", paintRect.width ) .add( "height", paintRect.height ) .add( "font", toJson( control.getFont() ) ) .add( "fillStyle", toJson( background ) ) .add( "strokeStyle", toJson( foreground ) ); getRemoteObject( getGcId( control ) ).call( "init", parameters ); operations = new JsonArray(); initialized = true; } } void write( GCOperation operation ) { initialize(); if( operation instanceof DrawLine ) { drawLine( ( DrawLine )operation ); } else if( operation instanceof DrawPoint ) { drawPoint( ( DrawPoint )operation ); } else if( operation instanceof DrawRoundRectangle ) { drawRoundRectangle( ( DrawRoundRectangle )operation ); } else if( operation instanceof FillGradientRectangle ) { fillGradientRectangle( ( FillGradientRectangle )operation ); } else if( operation instanceof DrawRectangle ) { drawRectangle( ( DrawRectangle )operation ); } else if( operation instanceof DrawArc ) { drawArc( ( DrawArc )operation ); } else if( operation instanceof DrawPolyline ) { drawPolyline( ( DrawPolyline )operation ); } else if( operation instanceof DrawImage ) { drawImage( ( DrawImage )operation ); } else if( operation instanceof DrawText ) { drawText( ( DrawText )operation ); } else if( operation instanceof DrawPath ) { drawPath( ( DrawPath )operation ); } else if( operation instanceof SetProperty ) { setProperty( ( SetProperty )operation ); } else if( operation instanceof SetClipping ) { setClipping( ( SetClipping )operation ); } else if( operation instanceof SetTransform ) { setTransform( ( SetTransform )operation ); } else { String name = operation.getClass().getName(); throw new IllegalArgumentException( "Unsupported GCOperation: " + name ); } } void render() { if( operations != null ) { if( !operations.isEmpty() ) { JsonObject parameters = new JsonObject().add( "operations", operations ); getRemoteObject( getGcId( control ) ).call( "draw", parameters ); } operations = null; } } private void drawLine( DrawLine operation ) { float offset = getOffset( false ); addClientOperation( "beginPath" ); addClientOperation( "moveTo", operation.x1 + offset, operation.y1 + offset ); addClientOperation( "lineTo", operation.x2 + offset, operation.y2 + offset ); addClientOperation( "stroke" ); } private void drawPoint( DrawPoint operation ) { float x = operation.x; float y = operation.y; addClientOperation( "save" ); operations.add( new JsonArray() .add( "fillStyle" ) .add( toJson( foreground ) ) ); addClientOperation( "lineWidth", 1 ); addClientOperation( "beginPath" ); addClientOperation( "rect", x, y, 1, 1 ); addClientOperation( "fill" ); addClientOperation( "restore" ); } private void drawRectangle( DrawRectangle operation ) { float offset = getOffset( operation.fill ); float x = operation.x + offset; float y = operation.y + offset; float width = operation.width; float height = operation.height; addClientOperation( "beginPath" ); addClientOperation( "rect", x, y, width, height ); addClientOperation( operation.fill ? "fill" : "stroke" ); } private void fillGradientRectangle( FillGradientRectangle operation ) { boolean vertical = operation.vertical; float width = operation.width; float height = operation.height; float x1 = operation.x; float y1 = operation.y; boolean swapColors = false; if( width < 0 ) { x1 += width; if( !vertical ) { swapColors = true; } } if( height < 0 ) { y1 += height; if( vertical ) { swapColors = true; } } RGB startColor = swapColors ? background : foreground; RGB endColor = swapColors ? foreground : background ; float x2 = vertical ? x1 : x1 + Math.abs( width ); float y2 = vertical ? y1 + Math.abs( height ) : y1; addClientOperation( "save" ); addClientOperation( "createLinearGradient", x1, y1, x2, y2 ); operations.add( new JsonArray() .add( "addColorStop" ) .add( 0 ) .add( toJson( startColor ) ) ); operations.add( new JsonArray() .add( "addColorStop" ) .add( 1 ) .add( toJson( endColor ) ) ); addClientOperation( "fillStyle", "linearGradient" ); addClientOperation( "beginPath" ); addClientOperation( "rect", x1, y1, width, height ); addClientOperation( "fill" ); addClientOperation( "restore" ); } private void drawRoundRectangle( DrawRoundRectangle operation ) { // NOTE: the added "+1" in arcSize is the result of a visual comparison of RAP to SWT/Win. float offset = getOffset( operation.fill ); float x = operation.x + offset; float y = operation.y + offset; float w = operation.width; float h = operation.height; float rx = ( ( float )operation.arcWidth ) / 2 + 1; float ry = ( ( float )operation.arcHeight ) / 2 + 1; addClientOperation( "beginPath" ); addClientOperation( "moveTo", x, y + ry ); addClientOperation( "lineTo", x, y + h - ry ); addClientOperation( "quadraticCurveTo", x, y + h, x + rx, y + h ); addClientOperation( "lineTo", x + w - rx, y + h ); addClientOperation( "quadraticCurveTo", x + w, y + h, x + w, y + h - ry ); addClientOperation( "lineTo", x + w, y + ry ); addClientOperation( "quadraticCurveTo", x + w, y, x + w - rx, y ); addClientOperation( "lineTo", x + rx, y ); addClientOperation( "quadraticCurveTo", x, y, x, y + ry ); addClientOperation( operation.fill ? "fill" : "stroke" ); } private void drawArc( DrawArc operation ) { double factor = Math.PI / 180; float offset = getOffset( operation.fill ); float rx = operation.width / 2; float ry = operation.height / 2; float cx = operation.x + rx + offset ; float cy = operation.y + ry + offset; float startAngle = round( operation.startAngle * factor * -1, 4 ); float arcAngle = round( operation.arcAngle * factor * -1, 4 ); addClientOperation( "save" ); addClientOperation( "beginPath" ); operations.add( new JsonArray() .add( "ellipse" ) .add( cx ) .add( cy ) .add( rx ) .add( ry ) .add( 0 ) .add( startAngle ) .add( startAngle + arcAngle ) .add( arcAngle < 0 ) ); if( operation.fill ) { addClientOperation( "lineTo", 0, 0 ); addClientOperation( "closePath" ); } addClientOperation( operation.fill ? "fill" : "stroke" ); addClientOperation( "restore" ); } private void drawPolyline( DrawPolyline operation ) { int[] points = operation.points; float offset = getOffset( operation.fill ); addClientOperation( "beginPath" ); for( int i = 0; i < points.length; i += 2 ) { if( i == 0 ) { addClientOperation( "moveTo", points[ i ] + offset, points[ i + 1 ] + offset ); } else { addClientOperation( "lineTo", points[ i ] + offset, points[ i + 1 ] + offset ); } } if( operation.close && points.length > 1 ) { addClientOperation( "lineTo", points[ 0 ] + offset, points[ 1 ] + offset ); } addClientOperation( operation.fill ? "fill" : "stroke" ); } private void drawImage( DrawImage operation ) { String path = ImageFactory.getImagePath( operation.image ); if( operation.simple ) { addClientOperation( "drawImage", path, operation.destX, operation.destY ); } else { addClientOperation( "drawImage", path, operation.srcX, operation.srcY, operation.srcWidth, operation.srcHeight, operation.destX, operation.destY, operation.destWidth, operation.destHeight ); } } private void drawText( DrawText operation ) { boolean fill = ( operation.flags & SWT.DRAW_TRANSPARENT ) == 0; boolean drawMnemonic = ( operation.flags & SWT.DRAW_MNEMONIC ) != 0; boolean drawDelemiter = ( operation.flags & SWT.DRAW_DELIMITER ) != 0; boolean drawTab = ( operation.flags & SWT.DRAW_TAB ) != 0; operations.add( new JsonArray() .add( fill ? "fillText" : "strokeText" ) .add( operation.text ) .add( drawMnemonic ) .add( drawDelemiter ) .add( drawTab ) .add( operation.x ) .add( operation.y ) ); } private void drawPath( DrawPath operation ) { renderPath( operation.types, operation.points ); addClientOperation( operation.fill ? "fill" : "stroke" ); } private void setProperty( SetProperty operation ) { String name; JsonValue value; switch( operation.id ) { case SetProperty.FOREGROUND: name = "strokeStyle"; foreground = ( RGB )operation.value; value = toJson( foreground ); break; case SetProperty.BACKGROUND: name = "fillStyle"; background = ( RGB )operation.value; value = toJson( background ); break; case SetProperty.ALPHA: float alpha = ( ( Integer )operation.value ).floatValue(); float globalAlpha = round( alpha / 255, 2 ); name = "globalAlpha"; value = JsonValue.valueOf( globalAlpha ); break; case SetProperty.LINE_WIDTH: name = "lineWidth"; int width = ( ( Integer )operation.value ).intValue(); width = width < 1 ? 1 : width; value = JsonValue.valueOf( width ); lineWidth = width; break; case SetProperty.LINE_CAP: name = "lineCap"; switch( ( ( Integer )operation.value ).intValue() ) { default: case SWT.CAP_FLAT: value = JsonValue.valueOf( "butt" ); break; case SWT.CAP_ROUND: value = JsonValue.valueOf( "round" ); break; case SWT.CAP_SQUARE: value = JsonValue.valueOf( "square" ); break; } break; case SetProperty.LINE_JOIN: name = "lineJoin"; switch( ( ( Integer )operation.value ).intValue() ) { default: case SWT.JOIN_BEVEL: value = JsonValue.valueOf( "bevel" ); break; case SWT.JOIN_MITER: value = JsonValue.valueOf( "miter" ); break; case SWT.JOIN_ROUND: value = JsonValue.valueOf( "round" ); break; } break; case SetProperty.FONT: name = "font"; value = toJson( ( FontData )operation.value ); break; default: String msg = "Unsupported operation id: " + operation.id; throw new RuntimeException( msg ); } operations.add( new JsonArray().add( name ).add( value ) ); } private void setClipping( SetClipping operation ) { if( operation.isReset() ) { addClientOperation( "restore" ); } else { addClientOperation( "save" ); if( operation.isRectangular() ) { Rectangle rect = operation.rectangle; addClientOperation( "beginPath" ); addClientOperation( "rect", rect.x, rect.y, rect.width, rect.height ); } else { renderPath( operation.types, operation.points ); } addClientOperation( "clip" ); } } private void setTransform( SetTransform operation ) { addClientOperation( "setTransform", operation.elements ); } private void renderPath( byte[] types, float[] points ) { addClientOperation( "beginPath" ); for( int i = 0, j = 0; i < types.length; i++ ) { switch( types[ i ] ) { case SWT.PATH_MOVE_TO: addClientOperation( "moveTo", points[ j++ ], points[ j++ ] ); break; case SWT.PATH_LINE_TO: addClientOperation( "lineTo", points[ j++ ], points[ j++ ] ); break; case SWT.PATH_CUBIC_TO: addClientOperation( "bezierCurveTo", points[ j++ ], points[ j++ ], points[ j++ ], points[ j++ ], points[ j++ ], points[ j++ ] ); break; case SWT.PATH_QUAD_TO: addClientOperation( "quadraticCurveTo", points[ j++ ], points[ j++ ], points[ j++ ], points[ j++ ] ); break; case SWT.PATH_CLOSE: addClientOperation( "closePath" ); break; default: String msg = "Unsupported point type: " + types[ i ]; throw new RuntimeException( msg ); } } } private void addClientOperation( String name, float... args ) { JsonArray operation = new JsonArray().add( name ); for( int i = 0; i < args.length; i++ ) { operation.add( args[ i ] ); } operations.add( operation ); } private void addClientOperation( String name, String argText, float... args ) { JsonArray operation = new JsonArray().add( name ).add( argText ); for( int i = 0; i < args.length; i++ ) { operation.add( args[ i ] ); } operations.add( operation ); } private float getOffset( boolean fill ) { float result = 0; if( !fill && lineWidth % 2 != 0 ) { result = ( float )0.5; } return result; } private Rectangle getPaintRect() { Rectangle paintRect = control.getAdapter( GCAdapter.class ).getPaintRect(); if( paintRect == null ) { Point size = control.getSize(); paintRect = new Rectangle( 0, 0, size.x, size.y ); } return paintRect; } static float round( double value, int decimals ) { int factor = ( int )Math.pow( 10, decimals ); return ( ( float )Math.round( factor * value ) ) / factor; } static String getGcId( Widget widget ) { return getId( widget ) + ".gc"; } }