import com.robertpenner.easing.Expo; import com.robertpenner.easing.Quad; import com.robertpenner.easing.Quart; import flash.display.BitmapData; import flash.filters.DropShadowFilter; import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; import flash.filters.GlowFilter; class com.anttikupila.debug.Recorder { // _________________________________________________________________________ // SETTINGS private var SPEED_WHEEL_WIDTH : Number = 12; private var SPEED_WHEEL_EFFECT : Number = 0.1; // _________________________________________________________________________ // VARIABLES static private var instance : Recorder; static private var _responseKey : Number = Key.SPACE; private var holder : MovieClip; private var mask : MovieClip; private var record : Boolean; private var recordData : Array; private var controls : MovieClip; private var selector : MovieClip; private var startPoint : Point; private var selectorEnabled : Boolean; private var selection : Rectangle; private var selectionMatrix : Matrix; private var clickToRecordBg : MovieClip; private var clickToRecordTf : TextField; private var recordFrame : MovieClip; private var controlPanel : MovieClip; private var cpBackground : MovieClip; private var recordInfo : TextField; private var playback : MovieClip; private var play : Boolean; private var playbackFrame : Number; private var playbackSpeed : Number = 1; private var animations : Object; private var animationId : Number; private var speedWheelHolder : MovieClip; private var speedWheel : MovieClip; private var speedWheelLinesHolder : MovieClip; private var speedWheelLinesArray : Array; private var speedDragStart : Number; private var speedScrubEnabled : Boolean; private var playbackSpeedAtScrub : Number; private var speedWheelSpin : Number; private var speedWheelShadow : MovieClip; private var currentWheelPosition : Number; private var speedIndicatorMc : MovieClip; private var speedIndicator : TextField; // _________________________________________________________________________ // PRIVATE CONSTRUCTOR private function Recorder( ) { var objRef : Recorder = this; Key.addListener( this ); holder = _root.createEmptyMovieClip( "recorder", _root.getNextHighestDepth( ) ); mask = holder.createEmptyMovieClip( "mask", holder.getNextHighestDepth( ) ); selector = holder.createEmptyMovieClip( "selector", holder.getNextHighestDepth( ) ); selector.beginFill( 0xFFFFFF, 20 ); selector.lineTo( 100, 0 ); selector.lineTo( 100, 100 ); selector.lineTo( 0, 100 ); selector.lineTo( 0, 0 ); selector.endFill( ); selector.blendMode = "invert"; selector.onRelease = function( ) { objRef.selectorClickHandler( ); }; clickToRecordBg = holder.createEmptyMovieClip( "clickToRecordBg", holder.getNextHighestDepth( ) ); clickToRecordTf = holder.createTextField( "clickToRecordTf", holder.getNextHighestDepth( ), 0, 0, 200, 15 ); clickToRecordTf.filters = [ new DropShadowFilter( 1, 90, 0x000000, 100, 0, 0, 0.5 ) ]; var format : TextFormat = new TextFormat( "_sans", 10, 0xffffff ); format.align = "center"; clickToRecordTf.setNewTextFormat( format ); clickToRecordTf.selectable = false; clickToRecordTf.autoSize = "center"; clickToRecordTf.wordWrap = true; clickToRecordTf.text = "CLICK TO START RECORDING"; clickToRecordBg.beginFill( 0x000000, 50 ); clickToRecordBg.lineTo( 100, 0 ); clickToRecordBg.lineTo( 100, 15 ); clickToRecordBg.lineTo( 0, 15 ); clickToRecordBg.endFill( ); controls = holder.createEmptyMovieClip( "controls", holder.getNextHighestDepth( ) ); controls.setMask( mask ); speedWheelHolder = controls.createEmptyMovieClip( "speedWheelHolder", controls.getNextHighestDepth( ) ); speedWheel = speedWheelHolder.createEmptyMovieClip( "speedWheel", speedWheelHolder.getNextHighestDepth( ) ); speedWheel.onPress = function( ) { objRef.speedScrubStart( ); }; speedWheel.onRelease = speedWheel.onReleaseOutside = function( ) { objRef.speedScrubEnd( ); }; speedWheelLinesHolder = speedWheelHolder.createEmptyMovieClip( "speedWheelLinesHolder", speedWheelHolder.getNextHighestDepth( ) ); speedWheelLinesHolder._x = 1.5; speedWheel.filters = [ new GlowFilter( 0xffffff, 100, 3, 0, 0.25, 3, true ) ]; controlPanel = controls.createEmptyMovieClip( "controlPanel", controls.getNextHighestDepth( ) ); controlPanel._visible = false; cpBackground = controlPanel.createEmptyMovieClip( "cpBackground", controlPanel.getNextHighestDepth( ) ); cpBackground.filters = [ new DropShadowFilter( 0, 0, 0x000000, 100, 1, 1, 0.5, 1, true ) ]; var cpMatrix : Matrix = new Matrix( ); cpMatrix.createGradientBox( 100, 20, Math.PI / 2 ); cpBackground.beginGradientFill( "linear", [ 0x222222, 0x191919, 0x111111, 0x000000 ], [ 100, 100, 100, 100 ], [ 0, 128, 129, 255 ], cpMatrix ); cpBackground.lineTo( 100, 0 ); cpBackground.lineTo( 100, 20 ); cpBackground.lineTo( 0, 20 ); cpBackground.lineTo( 0, 0 ); recordInfo = controlPanel.createTextField( "recordInfo", 1, 0, 0, 200, 15 ); format.align = "left"; recordInfo._x = 5; recordInfo._y = 1; recordInfo.filters = [ new DropShadowFilter( 1, 90, 0x000000, 100, 0, 0, 0.5 ) ]; recordInfo.setNewTextFormat( format ); recordInfo.selectable = false; recordFrame = holder.createEmptyMovieClip( "recordFrame", holder.getNextHighestDepth( ) ); playback = holder.createEmptyMovieClip( "playback", holder.getNextHighestDepth( ) ); speedWheelShadow = holder.createEmptyMovieClip( "speedWheelShadow", holder.getNextHighestDepth( ) ); var shadowMatrix : Matrix = new Matrix( ); shadowMatrix.createGradientBox( 20 + SPEED_WHEEL_WIDTH, 120, 0, 0, -10 ); speedWheelShadow.beginGradientFill( "radial", [ 0x000000, 0x000000 ], [ 75, 0 ], [ 0, 255 ], shadowMatrix ); speedWheelShadow.lineTo( 10, 0 ); speedWheelShadow.lineTo( 10, 100 ); speedWheelShadow.lineTo( 0, 100 ); speedWheelShadow.lineTo( 0, 0 ); speedWheelShadow.moveTo( 10 + SPEED_WHEEL_WIDTH, 0 ); speedWheelShadow.lineTo( 20 + SPEED_WHEEL_WIDTH, 0 ); speedWheelShadow.lineTo( 20 + SPEED_WHEEL_WIDTH, 100 ); speedWheelShadow.lineTo( 10 + SPEED_WHEEL_WIDTH, 100 ); speedWheelShadow.lineTo( 10 + SPEED_WHEEL_WIDTH, 0 ); speedWheelShadow.endFill( ); speedIndicatorMc = holder.createEmptyMovieClip( "speedIndicatorMc", holder.getNextHighestDepth( ) ); speedIndicatorMc.blendMode = "invert"; speedIndicator = speedIndicatorMc.createTextField( "speedIndicator", speedIndicatorMc.getNextHighestDepth( ), 0, 0, 200, 15 ); speedIndicator.setNewTextFormat( new TextFormat( "_sans", 18 ) ); speedIndicator.autoSize = true; speedIndicator.selectable = false; reset( ); selector.onEnterFrame = function( ) { objRef.frameHandler( ); }; } static public function initialize( ) : Void { if ( instance == null ) instance = new Recorder( ); } // _________________________________________________________________________ // PROTECTED METHODS private function reset( ) : Void { holder._visible = false; speedWheelHolder._visible = false; selector._visible = false; clickToRecordBg._visible = false; clickToRecordTf._visible = false; recordFrame._visible = false; controlPanel._visible = false; speedWheelShadow._visible = false; speedIndicatorMc._visible = false; holder._alpha = 0; speedIndicatorMc.setMask( null ); playbackSpeed = 1; speedScrubEnabled = false; selectorEnabled = selector.enabled = false; record = false; play = false; delete currentWheelPosition; for (var i : Number = 0; i < recordData.length; i++) { BitmapData( recordData[ i ] ).dispose( ); } recordData = new Array( ); holder.filters = [ ]; } private function initMask( ) : Void { mask.clear( ); mask.beginFill( 0xff00ff, 20 ); mask.moveTo( selection.width, 0 ); mask.lineTo( selection.width + 50, 0 ); mask.lineTo( selection.width + 50, selection.height + 50 ); mask.lineTo( 0, selection.height + 50 ); mask.lineTo( 0, selection.height ); mask.lineTo( selection.width, selection.height ); mask.lineTo( selection.width, 0 ); mask.endFill( ); } private function initControls( ) : Void { showRecordNotification( ); selector.enabled = true; } private function initRecordFrame( ) : Void { recordFrame.clear( ); recordFrame.lineStyle( 1, 0xFF0000, 100, true ); recordFrame.moveTo( -1, -1 ); recordFrame.lineTo( selection.width, -1 ); recordFrame.lineTo( selection.width, selection.height ); recordFrame.lineStyle( 1, 0xFF0000, 50, true ); recordFrame.lineTo( -1, selection.height ); recordFrame.lineStyle( 1, 0xFF0000, 100, true ); recordFrame.lineTo( -1, -1 ); recordFrame._visible = true; } private function showControlPanel( ) : Void { controlPanel._y = selection.height - controlPanel._height; cpBackground._width = selection.width; controlPanel._visible = true; animate( controlPanel, "_y", selection.height, 500, Expo.easeOut ); } private function showRecordNotification( ) : Void { clickToRecordTf._width = selection.width; clickToRecordBg._width = selection.width; clickToRecordBg._height = clickToRecordTf.textHeight + 4; clickToRecordBg._y = selector._height / 2 - clickToRecordBg._height / 2; clickToRecordTf._y = selector._height / 2 - clickToRecordTf._height / 2; clickToRecordBg._visible = true; clickToRecordTf._visible = true; } private function startRecording( ) : Void { initMask( ); initRecordFrame( ); selector._visible = false; clickToRecordBg._visible = false; clickToRecordTf._visible = false; selectionMatrix = new Matrix( ); selectionMatrix.translate( -selection.x, -selection.y ); showControlPanel( ); record = true; } private function stopRecording( ) : Void { record = false; recordFrame._visible = false; startPlayback( ); } private function startPlayback( ) : Void { var shadow : Object = { alpha: 0 }; animate( shadow, "alpha", 100, 5000, Quart.easeInOut, { scope: this, func: shadowTweenHandler }, { scope: this, func: shadowTweenHandler } ); showSpeedWheel( ); speedIndicatorMc._visible = true; speedIndicatorMc._x = -200; // hide indicator speedIndicatorMc._y = selection.height - 25; speedIndicatorMc.setMask( selector ); playbackFrame = 0; play = true; } private function showSpeedWheel( ) : Void { speedWheelHolder._x = selection.width - SPEED_WHEEL_WIDTH - 8; speedWheelHolder._visible = true; animate( speedWheelHolder, "_x", selection.width + 0.75, 1000, Expo.easeInOut ); var speedMatrix : Matrix = new Matrix( ); speedMatrix.createGradientBox( SPEED_WHEEL_WIDTH, selection.height + controlPanel._height, Math.PI / 2 ); var h : Number = selection.height + controlPanel._height; speedWheel.clear( ); speedWheel.lineStyle( 1, 0x000000, 35 ); speedWheel.beginGradientFill( "linear", [ 0x2c2c2c, 0x808080, 0xffffff, 0x808080, 0x2c2c2c ], [ 100, 100, 100, 100, 100 ], [ 0, 48, 128, 206, 255 ], speedMatrix ); speedWheel.lineTo( SPEED_WHEEL_WIDTH, 0 ); speedWheel.lineTo( SPEED_WHEEL_WIDTH, h ); speedWheel.lineTo( 0, h ); speedWheel.endFill( ); speedWheel.lineStyle( 0 ); speedWheel.moveTo( SPEED_WHEEL_WIDTH, 0 ); speedWheel.beginFill( 0x333333 ); speedWheel.curveTo( SPEED_WHEEL_WIDTH + 8, h / 2, SPEED_WHEEL_WIDTH, h ); speedWheel.lineTo( SPEED_WHEEL_WIDTH, 0 ); speedWheel.endFill( ); speedWheelShadow._x = selection.width - 10; speedWheelShadow._height = h; speedWheelShadow._alpha = 0; speedWheelShadow._visible = true; animate( speedWheelShadow, "_alpha", 50, 1500, Quart.easeInOut ); var lines : Number = Math.ceil( ( selection.height + controlPanel._height ) / 10 ); for ( var i : Number = 0; i < speedWheelLinesArray.length; i++ ) { speedWheelLinesArray[ i ].removeMovieClip( ); } var line0 : MovieClip = speedWheelLinesHolder.createEmptyMovieClip( "line1", speedWheelLinesHolder.getNextHighestDepth( ) ); line0.lineStyle( 1, 0x000000, 25 ); line0.lineTo( SPEED_WHEEL_WIDTH - 1.75, 0 ); line0.moveTo( 0, 1 ); line0.lineStyle( 1, 0xffffff, 15 ); line0.lineTo( SPEED_WHEEL_WIDTH - 1.75, 1 ); line0.cacheAsBitmap = true; speedWheelLinesArray = [ line0 ]; for ( var i : Number = 1; i < lines; i++ ) { speedWheelLinesArray.push( line0.duplicateMovieClip( "line" + i, speedWheelLinesHolder.getNextHighestDepth( ), { cacheAsbitmap: true } ) ); } setSpeedWheelLinePositions( ); } private function setSpeedWheelLinePositions( ) : Void { var newPos : Number = Math.round( ( playbackSpeed - 1 ) / SPEED_WHEEL_EFFECT * 1000 ) / 1000; if ( currentWheelPosition == newPos ) return; currentWheelPosition = newPos; for (var i : Number = 1; i <= speedWheelLinesArray.length; i++) { var pos : Number = i / speedWheelLinesArray.length * Math.PI + currentWheelPosition; while( pos > Math.PI ) pos -= Math.PI; while( pos < 0 ) pos += Math.PI; speedWheelLinesArray[ i - 1 ]._y = ( speedWheel._height - 2 ) / 2 + Math.cos( pos ) * ( selection.height + controlPanel._height - 4 ) / 2; } } private function animate( target : Object, property : String, value : Number, durationMs : Number, easeFunction : Function, updateFunction : Object, completeCallback : Object ) : Void { if ( animations == null ) { animations = new Object( ); animationId = 0; } var key : String; if ( target instanceof MovieClip ) { key = target._name; } else { if ( target.animationId == null ) target.animationId = ++animationId; key = target.animationId; } key += "_" + property; if ( updateFunction != null && updateFunction.args == null ) updateFunction.args = [ ]; if ( completeCallback != null && completeCallback.args == null ) completeCallback.args = [ ]; animations[ key ] = { target: target, property: property, value: value, duration: durationMs || 1000, startTime: getTimer( ), startValue: target[ property ], easeFunction: easeFunction, updateFunction: updateFunction, completeCallback: completeCallback }; } private function close( ) : Void { Mouse.removeListener( this ); animate( holder, "_alpha", 0, 500, Quart.easeInOut, null, { scope: this, func: recorderHidden } ); } private function showSpeedIndicator( ) : Void { animate( speedIndicatorMc, "_x", 0, 200, Expo.easeOut ); } // _________________________________________________________________________ // EVENT HANDLERS private function onKeyDown( ) : Void { if ( Key.getCode( ) == Key.ESCAPE ) { close( ); } if ( Key.getCode( ) == _responseKey ) { if ( !clickToRecordTf._visible && !record && !selector._visible && !play ) { reset( ); Mouse.addListener( this ); } else if ( record ) { stopRecording( ); } else if ( play ) { close( ); } } if ( play && !speedScrubEnabled ) { if ( Key.getCode( ) == Key.UP ) { speedWheelSpin = -10; showSpeedIndicator( ); } else if ( Key.getCode( ) == Key.DOWN ) { speedWheelSpin = 10; showSpeedIndicator( ); } } } private function onMouseDown( ) : Void { holder.swapDepths( _root.getNextHighestDepth( ) ); animate( holder, "_alpha", 100, 250 ); selectorEnabled = true; startPoint = new Point( _root._xmouse, _root._ymouse ); holder._visible = true; selector._visible = true; } private function onMouseUp( ) : Void { selectorEnabled = false; Mouse.removeListener( this ); initControls( ); } private function onMouseWheel( delta : Number ) : Void { playbackSpeed += delta / 100; } private function frameHandler( ) : Void { if ( selectorEnabled ) { var x1 : Number = Math.min( startPoint.x, _root._xmouse ); var y1 : Number = Math.min( startPoint.y, _root._ymouse ); var x2 : Number = Math.max( startPoint.x, _root._xmouse ); var y2 : Number = Math.max( startPoint.y, _root._ymouse ); holder._x = x1; holder._y = y1; selector._width = x2 - x1; selector._height = y2 - y1; selection = new Rectangle( x1, y1, x2 - x1, y2 - y1 ); } else if ( record ) { var frame : BitmapData = new BitmapData( selection.width, selection.height, false, 0x000000 ); frame.draw( _root, selectionMatrix ); recordData.push( frame ); recordInfo.text = recordData.length.toString( ); } else if ( play ) { // Process speedwheel speedIndicator.text = "Speed: " + Math.round( playbackSpeed * 100 ) + "%"; if ( speedScrubEnabled ) { playbackSpeed = Math.max( Math.min( playbackSpeedAtScrub + ( speedDragStart - _root._ymouse ) / 100 * SPEED_WHEEL_EFFECT, 10 ), -10 ); speedWheelSpin = _root._ymouse; } else { if ( speedWheelSpin != null ) { speedWheelSpin *= 0.9; playbackSpeed -= speedWheelSpin / 100 * SPEED_WHEEL_EFFECT; if ( Math.abs( speedWheelSpin ) < 0.1 ) { speedWheelSpin = null; animate( speedIndicatorMc, "_x", -speedIndicatorMc._width, 1000, Expo.easeInOut ); } } } setSpeedWheelLinePositions( ); // Process playback recordInfo.text = Number( Math.round( playbackFrame ) + 1 ) + " / " + recordData.length; playback.attachBitmap( recordData[ Math.floor( playbackFrame ) ], 1 ); playbackFrame+=playbackSpeed; if ( playbackFrame >= recordData.length ) playbackFrame = 0; if ( playbackFrame < 0 ) playbackFrame = recordData.length - 1; } // Process animations var t : Number = getTimer( ); for (var i : String in animations) { var a : Object = animations[ i ]; var ease : Function = a.easeFunction || Quad.easeInOut; var v : Number = ease( t - a.startTime, a.startValue, a.value - a.startValue, a.duration ); a.target[ a.property ] = v; if ( a.updateFunction != null ) a.updateFunction.func.apply( a.updateFunction.scope, a.updateFunction.args.concat( [ a.target, a.property, v ] ) ); if ( t - a.startTime >= a.duration ) { a.target[ a.property ] = a.value; if ( a.completeCallback != null ) a.completeCallback.func.apply( a.completeCallback.scope, a.completeCallback.args.concat( [ a.target, a.property, v ] ) ); delete animations[ i ]; } } } private function selectorClickHandler( ) : Void { selector.enabled = false; startRecording( ); } private function shadowTweenHandler( ) : Void { holder.filters = [ new DropShadowFilter( 2, 90, 0x000000, arguments[ 2 ], 20, 20, 0.6, 3 ) ]; } private function speedScrubStart( ) : Void { speedDragStart = _root._ymouse; playbackSpeedAtScrub = playbackSpeed; speedScrubEnabled = true; showSpeedIndicator( ); } private function speedScrubEnd( ) : Void { speedScrubEnabled = false; speedWheelSpin = _root._ymouse - speedWheelSpin; } private function recorderHidden( ) : Void { reset( ); } }