hackedteam/rcs-console

View on GitHub
src/it/ht/rcs/console/effects/Reflector.as

Summary

Maintainability
Test Coverage
package it.ht.rcs.console.effects
{
  // Reflector, by Narciso Jaramillo, nj_flex@rictus.com
  // Copyright 2006 Narciso Jaramillo
  
  // This program is free software; you can redistribute it and/or
  // modify it under the terms of the GNU General Public License
  // as published by the Free Software Foundation; either version 2
  // of the License, or (at your option) any later version.
  
  // This program is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  // GNU General Public License for more details.
  
  // You should have received a copy of the GNU General Public License
  // along with this program; if not, write to the Free Software
  // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  
  // Partly based on ReflectFilter.as by Trey Long, trey@humanwasteland.com.
  

    import flash.display.BitmapData;
    import flash.display.GradientType;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    
    import mx.core.UIComponent;
    import mx.events.FlexEvent;
    import mx.events.MoveEvent;
    import mx.events.ResizeEvent;
    
    // imports for handling blur
    import flash.filters.BitmapFilter;
    import flash.filters.BitmapFilterQuality;
    import flash.filters.BlurFilter;
    
    
    /**
     * A component that displays a reflection below another component. 
     * The reflection is "live"--as the other component's display updates,
     * the reflection updates as well.  The reflection automatically positions
     * itself below the target component (so it only works if the target
     * component's container is absolutely positioned, like a Canvas or a
     * Panel with layout="absolute").
     * 
     * Typically, you'll want to set a low alpha on the Reflector component (0.3
     * would be a good default).
     * 
     * Author: Narciso Jaramillo, nj_flex@rictus.com
     */
    public class Reflector extends UIComponent
    {
      // The component we're reflecting.
      private var _target: UIComponent;
      
      // Cached bitmap data objects.  We store these to avoid reallocating
      // bitmap data every time the target redraws.
      private var _alphaGradientBitmap: BitmapData;
      private var _targetBitmap: BitmapData;
      private var _resultBitmap: BitmapData;
      
      // The current falloff value (see the description of the falloff property).
      private var _falloff: Number = 0.6;
      
      // the current blur value
      private var _blurAmount:Number = 0.5;
      
      /**
       * The UIComponent that you want to reflect.  Should be in an absolutely-
       * positioned container.  The reflector will automatically position itself
       * beneath the target.
       */         
      [Bindable]
      public function get target(): UIComponent {
        return _target;
      }
      
      public function set target(value: UIComponent): void {
        if (_target != null) {
          // Remove our listeners from the previous target.
          _target.removeEventListener(FlexEvent.UPDATE_COMPLETE, handleTargetUpdate, true);
          _target.removeEventListener(MoveEvent.MOVE, handleTargetMove);
          _target.removeEventListener(ResizeEvent.RESIZE, handleTargetResize);
          
          // Clear our bitmaps, so we regenerate them next time a component is targeted.
          clearCachedBitmaps();
        }
        
        _target = value;
        
        if (_target != null) {
          // Register to get notified whenever the target is redrawn.  We pass "true" 
          // for useCapture here so we can detect when any descendants of the target are
          // redrawn as well.
          _target.addEventListener(FlexEvent.UPDATE_COMPLETE, handleTargetUpdate, true);
          
          // Register to get notified whenever the target moves or resizes.
          _target.addEventListener(MoveEvent.MOVE, handleTargetMove);
          _target.addEventListener(ResizeEvent.RESIZE, handleTargetResize);
          
          // Mark ourselves dirty so we get redrawn at the next opportunity.
          invalidateDisplayList();
        }
      }
      
      /**
       * How much of the component to reflect, between 0 and 1; 0 means not to
       * reflect any of the component, while 1 means to reflect the entire
       * component.  The default is 0.6.
       */
      [Bindable]
      public function get falloff(): Number {
        return _falloff;
      }
      
      public function set falloff(value: Number): void {
        _falloff = value;
        
        // Clear the cached gradient bitmap, since we need to regenerate it to
        // reflect the new falloff value.
        _alphaGradientBitmap = null;
        
        invalidateDisplayList();
      }
      
      [Bindable]
      public function get blurAmount(): Number {
        return _blurAmount;
      }
      
      public function set blurAmount(value: Number): void {
        _blurAmount = value;
        
        // Clear the cached gradient bitmap, since we need to regenerate it to
        // reflect the new falloff value.
        _alphaGradientBitmap = null;
        
        invalidateDisplayList();
      }
      
      private function handleTargetUpdate(event: FlexEvent): void {
        // The target has been redrawn, so mark ourselves for redraw.
        invalidateDisplayList();
      }
      
      private function handleTargetMove(event: MoveEvent): void {
        // Move to be immediately below the target.  We don't need to
        // redraw ourselves in this case.
        move(_target.x, _target.y + _target.height);
      }
      
      private function handleTargetResize(event: ResizeEvent): void {
        // Since the target is resizing, we have to recreate our bitmaps
        // in addition to redrawing and resizing ourselves.
        clearCachedBitmaps();
        width = _target.width;
        height = _target.height;
        invalidateDisplayList();
      }
      
      override protected function updateDisplayList(unscaledWidth: Number, unscaledHeight: Number): void {
        // This function is called by the framework at some point after invalidateDisplayList() is called.
        if (_target != null) {
          // Create our cached bitmap data objects if they haven't been created already.
          createBitmaps(_target);
          
          var rect: Rectangle = new Rectangle(0, 0, _target.width, _target.height);
          
          // Draw the image of the target component into the target bitmap.
          _targetBitmap.fillRect(rect, 0x00000000);
          _targetBitmap.draw(_target, new Matrix());
          
          // Combine the target image with the alpha gradient to produce the reflection image.
          _resultBitmap.fillRect(rect, 0x00000000);
          _resultBitmap.copyPixels(_targetBitmap, rect, new Point(), _alphaGradientBitmap);
          
          // Flip the image upside down.
          var transform: Matrix = new Matrix();
          transform.scale(1, -1);
          transform.translate(0, _target.height);
          
          // And blur it
          graphics.beginFill(0xFFCC00);
          graphics.drawRect(0, 0, _target.width, _target.height);
          graphics.endFill();
          var filter:BitmapFilter = new BlurFilter(_blurAmount*5, _blurAmount*10, BitmapFilterQuality.HIGH);
          var myFilters:Array = new Array();
          myFilters.push(filter);
          filters = myFilters;            
          
          // Finally, copy the resulting bitmap into our own graphic context.
          graphics.clear();
          graphics.beginBitmapFill(_resultBitmap, transform, false);
          graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
        }
      }
      
      private function clearCachedBitmaps(): void {
        _alphaGradientBitmap = null;
        _targetBitmap = null;
        _resultBitmap = null;
      }
      
      private function createBitmaps(target: UIComponent): void {
        if (_alphaGradientBitmap == null) {
          // Create and store an alpha gradient.  Whenever we redraw, this will be combined
          // with an image of the target component to create the "fadeout" effect.
          _alphaGradientBitmap = new BitmapData(target.width, target.height, true, 0x00000000);
          var gradientMatrix: Matrix = new Matrix();
          var gradientSprite: Sprite = new Sprite();
          gradientMatrix.createGradientBox(target.width, target.height * _falloff, Math.PI/2, 0, target.height * (1.0 - _falloff));
          gradientSprite.graphics.beginGradientFill(GradientType.LINEAR, [0xFFFFFF, 0xFFFFFF], [0, 1], [0, 255], gradientMatrix);
          gradientSprite.graphics.drawRect(0, target.height * (1.0 - _falloff), target.width, target.height * _falloff);
          gradientSprite.graphics.endFill();
          _alphaGradientBitmap.draw(gradientSprite, new Matrix());
        }
        if (_targetBitmap == null) {
          // Create a bitmap to hold the target's image.  This is updated every time
          // we're redrawn in updateDisplayList().
          _targetBitmap = new BitmapData(target.width, target.height, true, 0x00000000);
        }
        if (_resultBitmap == null) {
          // Create a bitmap to hold the reflected image.  This is updated every time
          // we're redrawn in updateDisplayList().
          _resultBitmap = new BitmapData(target.width, target.height, true, 0x00000000);
        }
      }
    }

}