blob: b4f5d73140d73eb02a5779d4318a51138ed2a70c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2012 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.animation.AnimationRenderer", {
extend : rwt.qx.Object,
construct : function( animation ) {
// Animation is responsible for the dispose:
this._autoDispose = false;
this.base( arguments );
this._animation = animation;
this._animation._addRenderer( this );
// Simple use:
this._converterFunction = null;
this._renderFunction = null;
this._context = null;
this._startValue = null;
this._endValue = null;
this._lastValue = null;
this._setupFunction = null;
this._cloneFrom = null;
this._active = true;
this._activeOnce = false;
// Widget integration:
this._invisibilityGetter = rwt.util.Functions.returnZero;
this._fullVisibilityValue = null;
this._autoStartEnabled = true;
this._renderType = null;
this._renderAdapter = null;
this._animationType = 0;
this._autoCheck = true;
},
destruct : function() {
this.clearAnimation();
this._animation._removeRenderer( this );
this._animation = null;
this._startValue = null;
this._endValue = null;
this._invisibilityGetter = null;
this._lastValue = null;
this._setupFunction = null;
this._converterFunction = null;
this._renderFunction = null;
this._context = null;
this._cloneFrom = null;
},
members : {
//////////////////////////
// Public API - simple use
// Converts transitionValue (usually between 0 and 1) to the render-value.
setConverter : function( type ) {
if( typeof type == "string" ) {
this._converterFunction = rwt.animation.AnimationRenderer.converter[ type ];
} else {
this._converterFunction = type;
}
},
// Sets the function that is called with the value to render.
setRenderFunction : function( func, context ) {
if( this._renderType == null ) {
this._renderFunction = func;
this._context = context;
}
},
renderValue : function( value ) {
this._renderFunction.call( this._context, value );
this._lastValue = value;
},
setStartValue : function( value ) {
this._startValue = value;
},
setEndValue : function( value ) {
this._endValue = value;
},
// The setup-function is called (if set) directly before the first frame of
// an Animation is rendered. It is the last chance to set startValue,
// endValue, renderFunction and converter before they are used.
// The first parameter is the "config" value from Animation.start().
// The second paramter will be the animationRenderer.
setSetupFunction : function( func ) {
this._setupFunction = func;
},
// Use lastValue from this renderer as transition-value.
setCloneFrom : function( renderer ) {
this._cloneFrom = renderer;
},
getAnimation : function() {
return this._animation;
},
getContext : function() {
return this._context;
},
getStartValue : function( value ) {
return this._startValue;
},
getEndValue : function( value ) {
return this._endValue;
},
// Returns the value that was last rendered.
getLastValue : function() {
return this._lastValue;
},
// Set to false to disable calls to setupFunction and renderFunction.
// Also disables widget-integration.
setActive : function( value ) {
if( this._active !== value ) {
if( this._animation.isRunning() ) {
throw "AnimationRenderer: Can not change \"active\" while running!";
}
this._active = value;
if( this._renderType !== null ) {
this._handleAnimationType();
}
}
},
isActive : function() {
return this._active;
},
// Sets active to false as soon as animation is finished
activateOnce : function() {
if( !this._activeOnce ) {
this.setActive( true );
this._activeOnce = true;
}
},
cancelActivateOnce : function() {
if( this._activeOnce ) {
this._activeOnce = false;
this.setActive( false );
}
},
//////////////////////////////////
// internals - called by Animation
_setup : function( config ) {
if( this._active ) {
if( this._context instanceof rwt.widgets.base.Widget && this._context._isCreated !== true ) {
if( this._context._isInGlobalElementQueue ) {
rwt.widgets.base.Widget.flushGlobalQueues();
} else {
throw new Error( "AnimationRenderer setup failed: Widget not ready." );
}
}
if( this._setupFunction != null ) {
this._setupFunction.call( this._context, config, this );
}
this._startValue = this._prepareValue( this._startValue );
this._endValue = this._prepareValue( this._endValue );
if( this._renderFunction == null || this._converterFunction == null ) {
throw new Error( "renderFunction or converterFunction missing" );
}
}
},
_render : function( transitionValue ) {
if( this._active ) {
var convertValue = this._cloneFrom != null
? this._cloneFrom.getLastValue()
: transitionValue;
try {
var value = this._converterFunction( convertValue, this._startValue, this._endValue );
this.renderValue( value );
} catch( e ) {
throw "AnimationRenderer failed: " + ( e.message ? e.message : e );
}
}
},
_finish : function( config ) {
if( this._active && config == "disappear" ) {
this._updateWidgetVisibility();
this._forceWidgetRenderer();
}
this.cancelActivateOnce();
},
_prepareValue : function( value ) {
var result = value;
switch( this._renderType ) {
case "backgroundColor":
if( typeof value == "string" ) {
if( value == "transparent" || value === "" || value.slice( 0, 4 ) === "rgba" ) {
result = null;
} else {
result = rwt.util.Colors.cssStringToRgb( value );
}
}
break;
case "backgroundGradient":
if( value ) {
var result = [];
for( var i = 0; i < value.length; i++ ) {
result[ i ] = [
value[ i ][ 0 ],
rwt.util.Colors.cssStringToRgb( value[ i ][ 1 ] )
];
}
}
break;
case "opacity":
result = ( value == null || value > 1 || value < 0 ) ? 1 : value;
break;
default:
result = value != null ? value : 0;
break;
}
return result;
},
///////////////////////////
// API - Widget integration
// The RenderType can currently be: "height", "opacity", "backgroundColor",
// "backgroundGradient". The AnimationTypes are defined in the statics.
animate : function( widget, renderType, animationType ) {
if( this._context != widget
|| this._renderType != renderType
|| this._animationType != animationType )
{
this.clearAnimation();
this._context = widget;
this._renderAdapter = widget.getAdapter( rwt.widgets.util.WidgetRenderAdapter );
this._renderType = renderType;
this._animationType = animationType;
this._renderFunction = this._renderAdapter.getOriginalRenderer( this._renderType );
var map = rwt.animation.AnimationRenderer.converterByRenderType;
this.setConverter( map[ this._renderType ] );
this._handleAnimationType();
}
},
clearAnimation : function() {
if( this._renderType !== null ) {
this._animationType = 0;
this._handleAnimationType();
this._renderType = null;
this.setRenderFunction( null, null );
this._renderAdapter = null;
}
},
isAnimated : function( type ) {
var result = false;
if( this._animationType > 0 && this._active ) {
var animated = type & this._animationType;
if( typeof type === "undefined" || animated !== 0 ) {
result = true;
}
}
return result;
},
setInvisibilityGetter : function( value ) {
this._invisibilityGetter = value;
},
// default is true
setAutoStart : function( value ) {
this._autoStartEnabled = value;
},
// Prevent autoStart if startValue/endValue are invalid. If set to false,
// the values can be set before or in the setupFunction is called.
// Default is true.
setAutoCheck : function( value ) {
this._autoCheck = value;
},
// Return the actual, planned or last known value from the widget.
getValueFromWidget : function() {
var result = null;
switch( this._renderType ) {
case "opacity":
result = this._context.getOpacity();
break;
case "height":
if( this._context.isCreated() && this._context._style.height ) {
result = parseInt( this._context._style.height, 10 );
} else {
result = this._context.getHeightValue();
this._context._computedHeightValue = null;
this._context._invalidatePreferredInnerHeight();
this._context._invalidatePreferredBoxHeight();
}
break;
case "top":
if( this._context.isCreated() && this._context._style.top ) {
result = parseInt( this._context._style.top, 10 );
} else {
result = this._context.getTopValue();
}
break;
case "left":
if( this._context.isCreated() && this._context._style.left ) {
result = parseInt( this._context._style.left, 10 );
} else {
result = this._context.getLeftValue();
}
break;
case "backgroundColor":
var bg = "backgroundColor";
var context = this._context;
if( context.getGfxProperty && context.getGfxProperty( bg ) ) {
result = context.getGfxProperty( bg );
} else if( context.getStyleProperty( bg ) ) {
result = context.getStyleProperty( bg );
} else {
result = null;
}
break;
case "backgroundGradient":
var context = this._context;
// NOTE : this is not necessarily the actually last rendered value, but converting from
// css3-syntax to rwt-gradient would be overkill. It shouldn't matter.
result = context.getBackgroundGradient();
break;
default:
throw "getValueFromWidget: " + this._renderType + " not supported!";
}
return result;
},
// Are current values valid for animation (after using prepareValue)
// Assumes that the given values ARE valid as a property of the renderType.
checkValues : function() {
var result;
switch( this._renderType ) {
case "backgroundGradient":
case "backgroundColor":
result = this._startValue != null && this._endValue != null;
break;
default:
result = true;
break;
}
// NOTE: Does not compare objects, i.e. gradients:
return result && this._startValue != this._endValue;
},
//////////////////////////////////
// Widget integration - internals
_handleAnimationType : function() {
if( this._animation.isRunning() ) {
throw "AnimationRenderer: Can not change animation while running!";
}
// Note: Conventional event-handler would not be able to prevent the
// actual rendering, therefore the functions are overwritten instead.
if( this.isAnimated() ) {
this._attachToApplyVisibility( true );
this._attachToWidgetRenderer( true );
} else {
this._attachToApplyVisibility( false );
this._attachToWidgetRenderer( false );
}
},
_attachToApplyVisibility : function( value ) {
if( value ) {
this._renderAdapter.addRenderListener( "visibility", this._onVisibilityChange, this );
this._context.addEventListener( "create", this._onCreate, this );
} else {
this._renderAdapter.removeRenderListener( "visibility", this._onVisibilityChange, this );
this._context.removeEventListener( "create", this._onCreate, this );
}
},
_attachToWidgetRenderer : function( value ) {
if( value ) {
this._renderAdapter.addRenderListener( this._renderType, this._onOriginalRenderer, this );
} else {
this._renderAdapter.removeRenderListener( this._renderType, this._onOriginalRenderer, this );
}
},
//////////////////////////////////////
// Widget integration - event handlers
_onVisibilityChange : function( originalArgs ) {
var value = originalArgs[ 0 ];
var allow = false;
if( value ) {
allow = this._onBeforeAppear();
} else {
if( !this._context.isCreated() ) {
this._animation.cancel(); // scheduled appear animation
}
if( this._context.isSeeable() ) {
allow = this._onBeforeDisappear();
} else {
allow = true;
}
}
return allow;
},
_onCreate : function() {
if( this._context.isDisplayable() ) {
this._onBeforeAppear();
}
},
_onBeforeAppear : function() {
if( this._context.isCreated() ) {
this._animation.skip();
} else {
this._animation.cancel();
}
var typeAppear = rwt.animation.AnimationRenderer.ANIMATION_APPEAR;
if( this._context.isCreated() && this.isAnimated( typeAppear ) ) {
this.setEndValue( this.getValueFromWidget() );
if( this._invisibilityGetter != null ) {
this.setStartValue( this._invisibilityGetter( this._context ) );
this._render( 0 );
}
this._autoStart( typeAppear );
}
return true;
},
_onBeforeDisappear : function() {
this._animation.skip();
var typeDisappear = rwt.animation.AnimationRenderer.ANIMATION_DISAPPEAR;
var result = !this.isAnimated( typeDisappear );
if( !result ) {
if( this._invisibilityGetter !== null ) {
this.setEndValue( this._invisibilityGetter( this._context ) );
}
this.setStartValue( this.getValueFromWidget() );
this._autoStart( typeDisappear );
}
return result;
},
_onOriginalRenderer : function( originalArgs ) {
var value = originalArgs[ 0 ];
var oldValue = originalArgs[ 1 ];
var result = false;
if( this._animation.isStarted() ) {
var config = this._animation.getConfig();
var endValue = this._endValue;
if( config == "change" || config == "appear" ) {
this.setEndValue( value );
}
if( endValue != this._endValue ) {
if( this._animation.isRunning() ) {
this.setStartValue( this.getLastValue() );
}
if( !this._animation.restart() ) {
result = true;
this.cancelActivateOnce();
}
}
} else {
var typeChange = rwt.animation.AnimationRenderer.ANIMATION_CHANGE;
if( this.isAnimated( typeChange ) && this._context.isSeeable() ) {
this.setStartValue( typeof oldValue !== "undefined"
? oldValue
: this.getValueFromWidget() );
this.setEndValue( value );
if( !this._autoStart( typeChange ) && this._autoStartEnabled ) {
result = true;
}
} else {
result = true;
}
}
return result;
},
//////////////////////////////
// Widget integration - helper
// Forces the widget to call the renderer, may be asynchronous due to flush.
_forceWidgetRenderer : function() {
var applyName = rwt.animation.AnimationRenderer.applyFunctionNames[ this._renderType ];
this._context[ applyName ]( this._context.get( this._renderType ) );
},
_autoStart : function( type ) {
var result = false;
if( this._autoStartEnabled
&& this.isAnimated( type )
&& ( this._autoCheck ? this.checkValues() : true ) )
{
result = this._animation.start( this._typeToConfig( type ) );
} else {
this.cancelActivateOnce();
}
return result;
},
_typeToConfig : function( type ) {
var result = null;
switch( type ) {
case rwt.animation.AnimationRenderer.ANIMATION_APPEAR:
result = "appear";
break;
case rwt.animation.AnimationRenderer.ANIMATION_DISAPPEAR:
result = "disappear";
break;
case rwt.animation.AnimationRenderer.ANIMATION_CHANGE:
result = "change";
break;
}
return result;
},
// calls the original "_applyVisibility".
_updateWidgetVisibility : function() {
var value = this._context.getVisibility();
this._renderAdapter.forceRender( "visibility", value );
}
},
statics : {
ANIMATION_APPEAR : 1,
ANIMATION_DISAPPEAR : 2,
ANIMATION_CHANGE : 4,
applyFunctionNames : {
"height" : "_applyHeight",
"opacity" : "_applyOpacity",
"backgroundColor" : "_applyBackgroundColor",
"backgroundGradient" : "_applyBackgroundGradient",
"top" : "_applyTop",
"left" : "_applyLeft"
},
converterByRenderType : {
"height" : "numericPositiveRound",
"top" : "numericRound",
"left" : "numericRound",
"opacity" : "factor",
"backgroundColor" : "color",
"backgroundGradient" : "gradient"
},
converter : {
// Converter working without startValue/EndValue
none : function( value ) {
return value;
},
round : Math.round,
positive : function( value ) {
return Math.max( 0, value );
},
// Converter needing valid startValue/EndValue
numeric : function( value, startValue, endValue ) {
return startValue + ( endValue - startValue ) * value;
},
numericRound : function( value, startValue, endValue ) {
var result = startValue + ( endValue - startValue ) * value;
return Math.round( result );
},
numericPositive : function( value, startValue, endValue ) {
var diff = endValue - startValue;
return Math.max( 0, startValue + diff * value );
},
numericPositiveRound : function( value, startValue, endValue ) {
var diff = endValue - startValue;
var result = Math.max( 0, startValue + diff * value );
return Math.round( result );
},
factor : function( value, startValue, endValue ) {
var result = startValue + ( endValue - startValue ) * value;
return Math.max( 0, Math.min( result, 1) );
},
color : function( value, startValue, endValue ) {
var result = [];
var part;
var partDiff;
for( var i = 0; i < 3; i++ ) {
partDiff = endValue[ i ] - startValue[ i ];
part = Math.round( startValue[ i ] + partDiff * value );
result[ i ] = Math.max( 0, Math.min( part, 255 ) );
}
return rwt.util.Colors.rgbToRgbString( result );
},
// Assumes that the number of colors are identical
gradient : function( value, startValue, endValue ) {
var convertColor = rwt.animation.AnimationRenderer.converter.color;
var convertFactor = rwt.animation.AnimationRenderer.converter.factor;
var result = [];
var length = Math.min( endValue.length, startValue.length );
for( var i = 0; i < length; i++ ) {
result[ i ] = [
convertFactor( value, startValue[ i ][ 0 ], endValue[ i ][ 0 ] ),
convertColor( value, startValue[ i ][ 1 ], endValue[ i ][ 1 ] )
];
}
return result;
}
}//converter
}
} );