Author Archive for Dale

ActionScript 3 Scrolling Panel Class

The NpScrollingPanel is a ActionScript 3 Implementation of a scrolling panel. Pretty common place stuff.

The NpScrollingPanel Class is designed to create a scrolling pane of content. The Class itself does not handle any loading in of content. You must provide the class with DisplayObjects for it to scroll. I wanted it to be completely content independent.

There’s actually two versions of the class in the download. One, which uses a mask for the scrolling area and another which uses a scrollRect. The mask performs better when you are scrolling with the blur filter enabled. Where as the scrollRect performs marginally better when just scrolling content. The scrollRect implementation also tends to keep memory useage slightly lower. One thing that using a scrollRect will cause is a slight jump in the scrolling as Flash clears the temp bitmapData it creates when creating the bitmap representation of the scrolled content. The advantage of the scrollRect approach is that you can use the blur filter when your scrolled content is larger than Flash’s 2880 px bitmapData limit, which you will come up against in the mask version. The reason for the scrollRect being slightly slower when using the blur is that it must iterate across each content item being scrolled and blur each one, rather than blurring the entire NpScrollingPanel instance. The reason for the iteration is that if you blur the entire instance, it blurs out past the boundaries of the clipped area, which can look cool or bad, depending on your point of view. I took the the bad view.
Anyway, you can use whatever version you like. They are functionally equivalent.

Let me know if there are bugs etc..

Class Features

  • Scroll x and y
  • Fixed or stage width / height scrolling area
  • Create columns / rows of scrolling content
  • Scroll via mouse movement, or via external sources
  • Scrolls any DisplayObject
  • Adjust content item padding on the fly
  • Remove content items on the fly
  • Use lots of cpu with a blur scroll mode :)
  • Simple internal easing engine
  • Control scroll speed and easing
  • Enable / Disable scrolling
  • Target individual content items

View Example
Here is an example. It’s using external XML content and the vertical scroller is pulling in two bitmaps from the library; http://www.noponies.com/dev/as3_scrollingpanel/

Source Files
Here are the relevant source file; http://www.blog.noponies.com/wp-content/uploads/npscrollingpanel.zip

Dependencies
The .fla’s use the Memory and Framerate monitoring component from Grant Skinner, which you can get here: http://www.gskinner.com/blog/archives/2008/04/simple_componen.html, otherwise, there are no dependencies.

Class ActionScript (just the main Scroller Class)

/* AS3
*Copyright 2008 noponies.
*/


package noponies.ui{

  import flash.display.Sprite;
  import flash.display.Shape;
  import flash.events.MouseEvent;
  import flash.events.Event;
 
  import flash.geom.Point;
  import flash.geom.Rectangle;

  import flash.display.DisplayObject;
  import flash.net.URLRequest;
  import flash.events.*;
  import flash.display.Loader;
  import flash.display.Bitmap;
  import flash.filters.*;

  import noponies.events.NpScrollingPanelEvent;
  import noponies.display.NpScrollingPanelItem;

  /**
  * <strong>NpScrollingPanel</strong>
  * <p>The NpScrollingPanel Class is designed to create a scrolling pane of content. The Class itself does not handle any loading in of content. You must
  * provide the class with DisplayObjects for it to scroll.</p>
  * <p>The Class supports scrolling in response to mouse movement over the panel, or via a public method.</p>
  * <p>It also suppors creating rows or columns of content and scrolling that. The Class also supports scaling its mask to browsers dimensions
  * or you can simply set it to a fixed size. You are also able to remove individual content items and the class will update the position
  * of the content items to accomodate items being removed.</p>
  * <p>Each content item is wrapped in a NpScrollingPanelItem Instance, which dispatches various custom mouse events. You can use these events to
  * track what content item has been clicked etc. Useful for using this class as part of a gallery etc.</p>
  *   <br /><br />
  * <b>Author:</b> noponies - <a href="http://www.blog.noponies.com/" target="_blank">www.blog.noponies.com</a><br />
  *   <b>Class version:</b> 1<br />
  *   <b>Actionscript version:</b> 3.0 Player Version 9.0.28<br />
  *   <b>Copyright:</b>
  *   Creative Commons AttCreative Commons Attribution 3.0 New Zealand License<br />
  *   <a href="http://creativecommons.org/licenses/by/3.0/nz/" target="_blank">http://creativecommons.org/licenses/by/3.0/nz/</a><br />
  *   <em>You can use this class how you like, except as a base for Flash Components or for Flash Template sites.</em><br />
  *   <br />
  *   <b>Date:</b> 03 September 2008<br />
  */

 
  public class NpScrollingPanel extends Sprite {
    //--------------------------------------
    // PRIVATE INSTANCE PROPERTIES
    //--------------------------------------
    private var maskWidth:int;
    private var maskHeight:int;
    private var counter:int;
    private var padding:int = 10;
    private var thumbYpos:int
    private var thumbXpos:int
    private var thumbMask:Sprite;
    private var thumbsHolder:Sprite;
    private var thumbsHolderBg:Sprite;
    private var itemArray:Array
    private var hitRect:Rectangle
    private var drawBg:Boolean = true
    private var bgColour:uint = 0xFFFFFF
    private var blurScroll:Boolean = false
    private var blurQual:int = 3
    //scroller variables
    private var useMouseScroll:Boolean = true;
    private var clipHalf:int;
    private var scrollSpeed:Number = .2
    private var pos:Number = 0;
    private var panelDirection:String = "horizontal";
    private var useStageDim:Boolean = true;
    private var mPos:int
    private var targPos:Number
    private var scrollingEnabled:Boolean
    private var mX:int
    private var mY:int
    private var cachePanelAsBit:Boolean = true
    private var blurAmount:Number = .4
    private var panelSnap:int = 3

    //--------------------------------------
    // GETTERS / SETTERS
    //--------------------------------------
    /**
     *  Get / Set the spacing between each content item within the NpScrollingPanel.
     *  <p><em>This is a Class Instantiation Property. But you can use the <code>adjustPadding()</code> method to change padding at runtime!</em></p>
     *  @default 10
     *  @return int
     */

    public function get contentPadding():int {
      return padding;
    }
    /**
    * @private
    */

    public function set contentPadding(newThumbPadding:int):void {
      padding = newThumbPadding;
    }
    /**
     *  Get / Set the <code>y</code> position of each thumbnail in within the NpScrollingPanel. The purpose of this property is to enable you to
     *  create mulit rows of content within the NpScrollingPanel.
     *  <p>To create a extra row of thumbnails within the NpScrollingPanel you would set both the <code>thumbYposition</code> and the <code>thumbXposition</code>
     *  properties. For instance, if you are wanting to scroll content horizontally, with two rows after say the 10th element, you would reset the <code>thumbXposition</code> to <code>0</code>
     *  as this property increments with each new thumbnail addition as well as changin the <code>thumbYposition</code> to reflect the new <code>y</code> value of the next row of content.</p>
     *  <p><em>This is a Class Instantiation Property</em></p>
     *  @default 0
     *  @return int
     */

    public function get contentYposition():int {
      return thumbYpos;
    }
    /**
    * @private
    */

    public function set contentYposition(newThumbYpos:int):void {
      thumbYpos = newThumbYpos;
    }
   
    /**
     *  Get / Set the <code>x</code> position of each thumbnail in within the NpScrollingPanel. The purpose of this property is to enable you to
     *  create mulit columns of content within the NpScrollingPanel.
     *  <p>To create a extra row of thumbnails within the NpScrollingPanel you would set both the <code>thumbXposition</code> and the <code>thumbYposition</code>
     *  properties. For instance, if you are wanting to scroll content vertically, with two rows after say the 10th element, you would reset the <code>thumbYposition</code> to <code>0</code>
     *  as this property increments with each new thumbnail addition as well as changin the <code>thumbXposition</code> to reflect the new <code>X</code> value of the next column of content.</p>
     *  <p><em>This is a Class Instantiation Property</em></p>
     *  @default 0
     *  @return int
     */

    public function get contentXposition():int {
      return thumbXpos;
    }
    /**
    * @private
    */

    public function set contentXposition(newThumbXpos:int):void {
      thumbXpos = newThumbXpos;
    }
   
    /**
     *  Get / Set the buffer that occurs at the start and end of the scrolling panel where the mouses positional value is rounded up or down depending on if its at the far left (down) or far right(up)
     *  of the scrolling panel. This setting makes it less fiddly to scroll to the very edges of the scrolled content. In effect it offsets the mouses position as it approaches the top-left, bottom-right of
     *  the scrolling panel. Setting a large value for this property will cause the panel to "snap" into position as the mouse reaches either of these positions.
     *  <p>This property has no effect on scrolling operations other than the via mouse movement.</p>
     *  <p><em>This is a Class Instantiation & run time Property</em></p>
     *  @default 3
     *  @return int
     */

    public function get panelSnapPoint():int {
      return panelSnap;
    }
    /**
    * @private
    */

    public function set panelSnapPoint(newPanelSnap:int):void {
      panelSnap = newPanelSnap;
    }
   
    /**
     *  Get / Set the visible width of the thumbs track. This value is sets the width of the mask that that thumbs scroll behind. Has no effect if
     *  the <code>resizeThumbMaskToStage</code> property is enabled and you are scrolling content in the <code>horizontal</code> direction.
     *  @default 0
     *  @return int
     *  @see #resizePanelMaskToStage
     */

    public function get panelMaskWidth():int {
      return maskWidth;
    }
    /**
    * @private
    */

    public function set panelMaskWidth(newMaskWidth:int):void {
      maskWidth = newMaskWidth;
    }
   
    /**
     *  Get / Set the visible height of the thumbs track. This value is sets the height of the mask that that thumbs scroll behind. Has no effect if
     *  the <code>resizePanelMaskToStage</code> property is enabled and you are scrolling content in the <code>vertical</code> direction.
     *  <p><em>You can set this property at runtime</em></p>
     *  @default 0
     *  @return int
     *  @see #resizePanelMaskToStage
     */

    public function get panelMaskHeight():int {
      return maskHeight;
    }
    /**
    * @private
    */

    public function set panelMaskHeight(newMaskHeight:int):void {
      maskHeight = newMaskHeight;
    }
   
    /**
     *  Get / Set the whether or not the thumb track stretches to match either the stages width, or stages height, depending on what orientation the NpScrollingPanel happens to be in.
     *  <p><em>Make sure you set the opposite track dimension value to the NpScrollingPanels orientation. So, if you are creating a horizontal thumb track, make sure you set the <code>maskHeight</code> property.</em></p>
     *  <p><em>You can set this property at runtime</em></p>
     *  @default 0 or stage.stageWidth
     *  @return Boolean
     */

    public function get resizePanelMaskToStage():Boolean {
      return useStageDim;
    }
    /**
    * @private
    */

    public function set resizePanelMaskToStage(newUseStageDim:Boolean):void {
      useStageDim = newUseStageDim;
    }
   
    /**
     *  Get / Set whether or not the thumb scrolling is controlled by a user moving their mouse over the NpScrollingPanel.
     *  <p><em>You can set this property at runtime</em></p>
     *  @default true
     *  @return Boolean
     */

    public function get mouseScrolling():Boolean {
      return useMouseScroll;
    }
    /**
    * @private
    */

    public function set mouseScrolling(newUseMouseScroll:Boolean):void {
      useMouseScroll = newUseMouseScroll;
    }
    /**
     *  Get / Set the easing for scrolling of the NpScrollingPanels scrolling.
     *  <p><em>You can set this property at runtime</em></p>
     *  @default .2
     *  @return Number
     */

    public function get scrollEase():Number {
      return scrollSpeed;
    }
    /**
    * @private
    */

    public function set scrollEase(newScrollMaxSpeed:Number):void {
      scrollSpeed = newScrollMaxSpeed;
    }
    /**
     *  Get / Set the position of the thumbsTrack as a <code>percentage!</code> of the NpScrollingPanels mask (visible area) width. Valid value range is within the <code>0-1</code> range.
     *  <p>Use this property to scroll the thumbsTrack via external means, such as a slider bar or a mouse Click on a button</p>
     *  <p><em>You can set this property at runtime</em></p>
     *  @default 0
     *  @return Number
     */

    public function get panelScrollPos():Number {
      if (panelDirection=="horizontal") {
        return - thumbsHolder.x / (thumbsHolder.width - thumbMask.width);
      } else {
        return - thumbsHolder.y / (thumbsHolder.height - thumbMask.width);
      }
    }
    /**
    * @private
    */

    public function set panelScrollPos(newTrackPos:Number):void {
      if(!scrollingEnabled){
        return
      }
      //test thumbsHolder bounds against thumbMask, thumbsHolder must be larger than mask
      if((panelDirection=="horizontal" && thumbsHolder.width<=thumbMask.width)||(panelDirection=="vertical" && thumbsHolder.height<=thumbMask.height)){
        return
      }
      //check to see if we have an enterframe event running, if not add it
      if (! this.willTrigger(Event.ENTER_FRAME)) {
        this.addEventListener(Event.ENTER_FRAME,entFrameScroll);
      }
      panelDirection=="horizontal" ? scrollThumbsX(newTrackPos) : scrollThumbsY(newTrackPos)
    }
   
    /**
     *  Get / Set if you want to blur the scrolled content as it scrolls. The amount of blur depends on how far the content has to scroll. Bigger distances
     *  equate to bigger blurs. Content is blurred in the direction it is scrolling in.
     *  <p><strong>Blurring will not work if your content is larger in any dimension that 2880 pixels. This is a Flash limit for bitmapData.</strong></p>  
     *  <p><em>Be aware that this can affect peformance</em></p>
     *  <p><em>You can set this property at runtime</em></p>
     *  @default false
     *  @return Boolean
     *  @see #panelBlurQuality
     */

    public function get panelScrollBlur():Boolean {
      return blurScroll
    }
    /**
    * @private
    */

    public function set panelScrollBlur(newBlur:Boolean):void {
      blurScroll = newBlur
    }
   
    /**
     *  Get / Set the quality of the blur if you have enabled the <code>panelScrollBlur</code> property. Higher numbers set higher quality blurs but degrade performance!
     *  <p><em>Be aware that this high will affect peformance</em></p>
     *  <p><em>You can set this property at runtime</em></p>
     *  @default 3
     *  @return int
     *  @see panelScrollBlur
     */

    public function get panelBlurQuality():int {
      return blurQual
    }
    /**
    * @private
    */

    public function set panelBlurQuality(newBlurQual:int):void {
      blurQual = newBlurQual
    }
   
    /**
     *  Get / Set the amount of the blurring if you have enabled the <code>panelScrollBlur</code> property. Higher numbers create greater amounts of blur!
     *  <p>Blurring is a proportion of the distance the panel has to travel. The effect of this property is to divide that amount to arrive at the level of blur.
     *  So, if you set a amount of <code>.5</code> then the blur will be calculated as .5 of that distance.</p>
     *  <p><em>You can set this property at runtime</em></p>
     *  @default .4
     *  @return Number
     *  @see panelScrollBlur
     */

    public function get panelBlurAmount():Number {
      return blurAmount
    }
    /**
    * @private
    */

    public function set panelBlurAmount(newBlurAmount:Number):void {
      blurAmount = newBlurAmount
    }
   
    /**
     *  Get / Set whether or not to cache the scrolling content as a whole as a bitmap. This increases performance, but you will suffer a performance hit if your content
     *  visually changes etc.
     *  <p>If you have enabled the <code>panelScrollBlur</code> property, the scrolling content will be bitmap cached when it is blurred, and returned to is previous cache state
     *  when the blur has finished.</p>
     *  <p><em>This property is tested each time the <code>enableScrolling</code> method is called.</em></p>
     *  @default true
     *  @return Boolean
     */

    public function get renderPanelAsBitmap():Boolean {
      return cachePanelAsBit
    }
    /**
    * @private
    */

    public function set renderPanelAsBitmap(newCachePanelAtBit:Boolean):void {
      cachePanelAsBit = newCachePanelAtBit
    }
   

    //--------------------------------------
    // CONSTRUCTOR
    //--------------------------------------
    /**
    * <strong>NpScrollingPanel</strong>
    * The NpScrollingPanel Class Constructor takes one argument. A string representing the direction you want to scroll your content within.
    * <p>To add content to this scrollingPanel use the <code>addContentItem()</code> method. When you have finished adding in your content that you wish
    * to scroll, call the <code>enableScrolling()</code> method.</p>
    * <p>Set the <code>mouseScrolling, scrollEase, resizeThumbMaskToStage, panelMaskWidth, panelMaskHeight, contentPadding</code> properties before you call the <code>addContentItem()</code> method!</p>
    * <p>Use the <code>contentYposition</code> and <code>contentYposition</code> properties as you add content to the NpScrollingPanel class </em>if</em> you want to create multiple rows and columns</p>
    * <p>If you need to access individual contentItems, you can access their various dispatched events, and from those events, hook into the event.target property. You can also simply  query the
    * <code>accessThumb()</code> method.
    * @param String (Optional) Representing the direction you want to scroll in. The default scrolling direction is <code>horizontal</code>!.
    * @see #mouseScrolling
    * @see #scrollEase
    * @see #resizeThumbMaskToStage
    * @see #panelMaskHeight
    * @see #panelMaskWidth
    * @see #contentPadding 
    * @see #renderPanelAsBitmap
    * @see #panelScrollBlur
    * @see enableScrolling()
    * @see #noponies.events.NpScrollingPanelEvent
    * @return
    * <br>
    * @example Example Useage
    *   <listing version="3.0">
    * //Create a new instance of NpScrollingPanel Class and populate it with bitmaps from the library.
    *
    * var thumbsContainer:NpScrollingPanel = new NpScrollingPanel();
    * thumbsContainer.resizePanelMaskToStage = true
    * thumbsContainer.panelMaskHeight = 200
    * addChild(thumbsContainer);
    *
    * var libraryItem:Bitmap;
    *
    * function createNewPanel():void {
    *  for (var i:int = 0; i< 15; i++) {
    *   var n:int=i%2;
    *   if (n==0) {
    *    var FireybreathData:Fireybreath = new Fireybreath(0, 0);
    *    libraryItem = new Bitmap(FireybreathData);
    *   } else {
    *    var StrawberryData:Strawberry = new Strawberry(0, 0);
    *    libraryItem = new Bitmap(StrawberryData);
    *   }
    *   thumbsContainer.addContentItem(libraryItem)
    *   //when we have finished adding content, turn on scrolling
    *   if(i==15){
    *   thumbsContainer.enableScrolling()
    *  }
    *  } 
    * }
    *
    * createNewPanel()
    *
    * </listing>
    */

    public function NpScrollingPanel(panelDirection:String = "horizontal") {
      switch (panelDirection.toLowerCase()) {
        case "horizontal" :
          this.panelDirection = panelDirection.toLowerCase();
          break;
        case "vertical" :
          this.panelDirection = panelDirection.toLowerCase();
          break;
        default :
        throw new Error("Problem :The direction parameter passed "+"\""+ panelDirection+"\"" + " does not match the allowed orientation modes, which are: \"horizontal\", \"vertical\"");
      };
     
      init();
    }
 
  //--------------------------------------
  //
  // PUBLIC METHODS
  //
  //--------------------------------------

  //--------------------------------------
  // ADD CONTENT ITEM
  //-------------------------------------- 
  /**
  *   <p>The <code>addContentItem()</code> public method adds DisplayObjects to the NpScrollingPanel.
  * @param DisplayObject Representing the DisplayObject you want to add to the NpScrollingPanel
  * @see #contentPadding
  * @see #contentXposition
  * @see #contentYposition
  * @return
  */

  public function addContentItem(thumbContent:DisplayObject):void {
    var newThumb:NpScrollingPanelItem= new NpScrollingPanelItem(thumbContent, counter, panelDirection);
    thumbsHolder.addChild(newThumb);

    if (panelDirection=="horizontal") {
      newThumb.x = thumbXpos;
      newThumb.y = thumbYpos;
    } else {
      newThumb.x = thumbXpos;
      newThumb.y = thumbYpos;
    }
   
    //create the mask
    if (counter==0) {
      if (useStageDim&&panelDirection=="horizontal") {
        maskWidth = stage.stageWidth;
       
      }
      if (useStageDim&&panelDirection=="vertical") {
        maskHeight = stage.stageHeight;
      }
     
      //create the mask sprite for the thumbs track
      thumbMask = new Sprite();
      thumbMask.graphics.beginFill(0xFFFFFF);
      thumbMask.graphics.drawRect(0, 0, maskWidth, maskHeight);
      thumbMask.graphics.endFill();
      thumbMask.x = 0;
      thumbMask.y = 0;
      addChild(thumbMask);
      this.mask = thumbMask;
     
      //set the bg of the thumbs track which is needed to create a smooth scrolling experience to match
      //the heights and widths of the first instance of the loaded in content
      //this sprite is resized again, as the content loads
      thumbsHolderBg.height = thumbContent.height;
      thumbsHolderBg.width = thumbContent.width;
    }
   
    //increment the positional counter for the thumbs positions
    if (panelDirection=="horizontal") {
      thumbsHolderBg.width = thumbsHolder.width;
      thumbsHolderBg.height = maskHeight;
      thumbXpos+=thumbContent.width+padding;
    } else {
      thumbsHolderBg.height = thumbsHolder.height;
      thumbsHolderBg.width = maskWidth;
      thumbYpos+=thumbContent.height+padding;
    }
   
    //create the counter
    counter++;
    itemArray.push(newThumb)
  }
 
  //--------------------------------------
  // ENABLE SCROLLING
  //-------------------------------------- 
  /**
  *   <p>The <code>enableScrolling()</code> public method will enable both mouse movement (Depending on if you have enabled the <code>mouseScrolling</code> property.</code>) based and setter based scrolling.
  * @see #mouseScrolling
  * @see disableScrolling()
  * @return
  */

  public function enableScrolling():void { 
      //enable mouse based scrolling, if it desired. Note the slight inflation of the rectangle. We need to offset it slightly as Flash tests
      //for less than when running a rectangle.contains() method.
      panelDirection=="horizontal" ? hitRect = new Rectangle(0, 0, maskWidth+1, maskHeight) : hitRect = new Rectangle(0, 0, maskWidth, maskHeight+1)

      if (useMouseScroll) {
        addScrollListeners();
      }
    scrollingEnabled = true
    //check to see if we are cacheing the content
    cachePanelAsBit ? thumbsHolder.cacheAsBitmap = true : thumbsHolder.cacheAsBitmap = false
   
  }

  //--------------------------------------
  // DISABLE SCROLLING
  //--------------------------------------
  /**
  *   <p>The <code>disableScrolling()</code> public method will disable both mouse movement based and setter based scrolling.
  * @see enableScrolling() 
  * @return
  */

  public function disableScrolling():void {
    if (this.willTrigger(Event.ENTER_FRAME)) {
      this.removeEventListener(Event.ENTER_FRAME, entFrameScroll);
    }
    scrollingEnabled = false
  }
 
  //--------------------------------------
  // REMOVE A PARTICULAR THUMB
  //--------------------------------------
  /**
  *   <p>The <code>removeContentItem()</code> public method will remove a contentItem from the NpScrollingPanel. ContentItems with either a
  * greater x or y (depending on scrolling direction) than the removed item will move their positions to fill the 'gap' left my deleting
  * the contentItem.</p>
  * <p>!! Removing content items when you have multi row or column scrollable content will possibly cause errors</p>
  * <p>Pass in the ID of the thumbnail you want to delete. When thumbs are added to the thumbsTrack they are allocated an ID, which corresponds
  * to the order they are added in.</p>
  * @param int Representing the ID of the thumbnail you want to delete from the thumbsTrack.
  * @return
  */

  public function removeContentItem(id:int):void {
    var thumbToDeleteDim:Number
    panelDirection=="horizontal" ? thumbToDeleteDim = itemArray[id].thumbWidth : thumbToDeleteDim = itemArray[id].thumbHeight
    thumbsHolder.removeChild(itemArray[id])
    itemArray.splice(id,1) 
   
    //iterate through elements contained in our itemArray
    var len:int = itemArray.length
    for (var i:int = 0; i< len; i++) {
      itemArray[i].removeClip(i, -thumbToDeleteDim-padding)
      }
    if(drawBg){
      thumbsHolderBg.width -= thumbToDeleteDim+padding;
    }
  }
 
  //--------------------------------------
  // ADJUST THUMB PADDING
  //--------------------------------------
  /**
  *   <p>The <code>adjustPadding()</code> public method will adjust the padding between each content item at runtime. Calling this method will
  * result in the content items tweening in unison to their new positions.</p>
  * @param int Representing the amount of padding you would like between each thumbnail.
  * @see enableScrolling() 
  * @return
  */

  public function adjustPadding(newPadding:int):void {
   
    var len:int = itemArray.length
    //iterate through elements contained in our itemArray
    for (var i:int = 0; i< len; i++) {
      itemArray[i].adjustPadding(newPadding)
      }
    if(drawBg){
      panelDirection=="horizontal" ? thumbsHolderBg.width+= (newPadding*itemArray.length)-newPadding : thumbsHolderBg.height+= (newPadding*itemArray.length)-newPadding;
    }
  }
 
  //--------------------------------------
  // UNLOAD ALL THUMBS
  //-------------------------------------- 
  /**
  *   <p>The <code>removeAllContent()</code> public method will unload any thumbnails currently loaded. It will also reset the heights and widths
  * of the NpScrollingPanel. Any mouse event listeners registered with the NpScrollingPanel will also be removed.</p>
  * <p>You would use this method if you wanted to repopulate the class with a new set of content.</p>
  * @return
  */

  public function removeAllContent():void {
    //deal with listeners first  
    if (this.willTrigger(Event.ENTER_FRAME)) {
      this.removeEventListener(Event.ENTER_FRAME, entFrameScroll);
    }
    if (useMouseScroll) {
        this.removeEventListener(MouseEvent.ROLL_OVER, mouseScroll);
    }
    //clear thumbs
    if (thumbsHolder.numChildren >1) {
      while (thumbsHolder.numChildren >1) {
        thumbsHolder.removeChildAt(1);
      }
    }
    //reset internal props
    counter=0;
    thumbsHolder.width = thumbsHolder.height = 0;
    if(drawBg){
      thumbsHolderBg.width = thumbsHolderBg.height = 0;
    }
    thumbYpos = thumbXpos = 0
    this.mask = null;
    removeChild(thumbMask);
    itemArray = [] 
  }
  //--------------------------------------
  // REFERENCE A PARTICULAR THUMB
  //--------------------------------------   
 
  /**
  *   <p>The <code>accessThumb()</code> public method is a means of gaining access to a particular thumbNail within the thumbsTrack.</p>
  * <p>You would use this method if you wanted to manipulate individual thumbNails.</p>
  * @return Object representing the thumbNail ID requested.
  */

  public function accessThumb(id:int):Object{
    return(itemArray[id])
  }
 
 
  //--------------------------------------
  //
  // PRIVATE METHODS
  //
  //--------------------------------------
 
  //--------------------------------------
  // INIT HANDLER - CALLED FROM CONSTRUCTOR
  //--------------------------------------
  //create thumbs holder sprite here, just do this once
 
  private function init():void {
    itemArray = []
    thumbsHolder = new Sprite();
    addChild(thumbsHolder);
    addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
  }
 
  //--------------------------------------
  // ADDED TO STAGE HANDLER
  //--------------------------------------
 
  private function addedToStageHandler(event:Event):void {
    if(drawBg){
      thumbsHolderBg = new Sprite();
      thumbsHolderBg.graphics.beginFill(bgColour,1);
      thumbsHolderBg.graphics.drawRect(0, 0, 1, 1);
      thumbsHolderBg.graphics.endFill();
      thumbsHolder.addChildAt(thumbsHolderBg,0);
      thumbsHolderBg.cacheAsBitmap = true
    }
    //add the stage resize listener if its required
    if(useStageDim){
      stage.addEventListener(Event.RESIZE, resizeHandler);
    }  
    addEventListener(Event.REMOVED_FROM_STAGE, handleRemovedFromStage)
  }
 
  //--------------------------------------
  // RESIZE HANDLER
  //--------------------------------------
 
  private function resizeHandler(event:Event):void {
    if (useStageDim) {
      if (panelDirection=="horizontal") {
        if (thumbsHolder.x+thumbsHolder.width<stage.stageWidth) {
          thumbsHolder.x = stage.stageWidth-thumbsHolder.width;
        }
        //test for when the stage is resized when the thumbs are loading
        if(thumbsHolder.width<=stage.stageWidth){
          thumbsHolder.x = 0
        }
        thumbMask.width = stage.stageWidth;
        thumbMask.height = maskHeight
        hitRect = new Rectangle(1, 0, thumbMask.width-1, maskHeight)
      } else {
        if (thumbsHolder.y+thumbsHolder.height<stage.stageHeight) {
          thumbsHolder.y = stage.stageHeight-thumbsHolder.height;
        }
        //test for when the stage is resized when the thumbs are loading
        if(thumbsHolder.height<=stage.stageHeight){
          thumbsHolder.x = 0
        }
        thumbMask.height = stage.stageHeight;
        thumbMask.width = maskWidth
        hitRect = new Rectangle(1, 0, maskWidth, thumbMask.height-1)
      }
    }
  }

  //--------------------------------------
  //
  // MOUSESCROLLING FUNCTIONS
  //
  //--------------------------------------
  //function that simply adds the event listeners to our scroll track
  //we call this when we have loaded in all the thumbs, as this stops a user from being
  //able to scroll a half empty thumbnails track, which would look crap.
 
  private function addScrollListeners():void {
    addEventListener(MouseEvent.ROLL_OVER, mouseScroll);
  }
 
  //handlers that either add or delete the scroll enterframe event
  private function mouseScroll(event:MouseEvent = null):void {
    if(!scrollingEnabled){
        return
      }
    //test thumbsHolder bounds against thumbMask, thumbsHolder must be larger than mask
    if((panelDirection=="horizontal" && thumbsHolder.width<=thumbMask.width)||(panelDirection=="vertical" && thumbsHolder.height<=thumbMask.height)){
        return
      }
    //check to see if we have an enterframe event running, if not add it
    if (! this.willTrigger(Event.ENTER_FRAME)) {
      this.addEventListener(Event.ENTER_FRAME,entFrameScroll);
    }
  }
 
  //--------------------------------------
  // ENTERFRAME HANDLER
  //-------------------------------------- 
 
  private function entFrameScroll(event:Event):void{
    //we have content to scroll, so lets scroll it!
    panelDirection=="horizontal" ? scrollThumbsX(0) : scrollThumbsY(0)
  }

  //--------------------------------------
  // SCROLLING IN THE X DIRECTION
  //-------------------------------------- 

  private function scrollThumbsX(nPos:Number = 0):void {
    //store mouse pos as vars
    mX = mouseX;
    mY = mouseY;
   
    //adjust mousePos inline with panelSnappingPoint
    if(mX > maskWidth-panelSnap) mX = maskWidth
    if(mX < panelSnap) mX = 0

    //set value if scrolling from a source other than mouse
    if (nPos != 0) {
      mPos = hitRect.width*nPos;
    }else if (useMouseScroll && hitRect.contains(mX,mY)) {
      if (mX < 0)  mX = 0;
      mPos = mX;
    }
   
    targPos = -(mPos/(hitRect.width/(thumbsHolder.width - hitRect.width)));
    thumbsHolder.x += Number(((targPos-thumbsHolder.x)) * scrollSpeed);

    //dampening & kill event handler when we are belowing 1px movement increments
    var n:Number = thumbsHolder.x- targPos;
    if (n < 0)  n = -n;
 
    //turn on blurring, if its being used, if its being used and only if we have a big enough movement in the panel
     if(blurScroll && mPos >= 0 && n > 1){
      thumbsHolder.filters=[new BlurFilter((n * blurAmount)*.5,0,blurQual)];
     }
   
    //dispatch the CONTENT_SLIDING event
    dispatchEvent(new NpScrollingPanelEvent(NpScrollingPanelEvent.SCROLLING,true, false, undefined, panelScrollPos));
   
    if (n < 1) {
      if (!useMouseScroll ||!hitRect.contains(mX,mY)) {
        this.removeEventListener(Event.ENTER_FRAME, entFrameScroll);
        if(blurScroll){
          thumbsHolder.filters=[];
        }
      }
    }
  }
  //--------------------------------------
  // SCROLLING IN THE Y DIRECTION
  //--------------------------------------   
 
  private function scrollThumbsY(nPos:Number = 0):void {
    //store mouse pos as vars
    mX = mouseX;
    mY = mouseY;
   
    //adjust mousePos inline with panelSnappingPoint
    if(mY > maskHeight-panelSnap) mY = maskHeight
    if(mY < panelSnap) mY = 0
   
    //set value if scrolling from a source other than mouse
    if (nPos!=0) {
      mPos =hitRect.height*nPos;
    }else if (useMouseScroll && hitRect.contains(mX,mY)) {
      if (mY < 0)  mY = 0;
      mPos = mY;
    }
   
    targPos = -(mPos/(hitRect.height/(thumbsHolder.height-hitRect.height)));
    thumbsHolder.y+=Number(((targPos-thumbsHolder.y))*scrollSpeed);

    //dampening & kill event handler when we are belowing 1px movement increments
    var n:Number = thumbsHolder.y- targPos;
    if (n < 0)  n = -n;
 
    //turn on blurring, if its being used and only if we have a big enough movement in the panel
     if(blurScroll && mPos >= 0 && n > 1){
      thumbsHolder.filters=[new BlurFilter(0,(n * blurAmount)*.5,blurQual)];
     }
    
     //dispatch the CONTENT_SLIDING event
    dispatchEvent(new NpScrollingPanelEvent(NpScrollingPanelEvent.SCROLLING,true, false, undefined, panelScrollPos));
    if (n < 1) {
      if (!useMouseScroll ||!hitRect.contains(mX,mY)) {
        this.removeEventListener(Event.ENTER_FRAME, entFrameScroll);
        if(blurScroll){
          thumbsHolder.filters=[];
        }
      }
    }
  }
 
  //--------------------------------------
  // HANDLE A REMOVED FROM STAGE EVENT
  //-------------------------------------- 

  //clean up the class when removed from stage
  private function handleRemovedFromStage(event:Event):void {
    try {
      removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler)
      removeEventListener(Event.REMOVED_FROM_STAGE, handleRemovedFromStage)
      if(useStageDim){
        stage.removeEventListener(Event.RESIZE, resizeHandler);
      }  
      //call public removeAllContent Method
      removeAllContent()

      } catch (event:Error) {
      trace("There was an error when attempting to delete all NpScrollingPanel content: "+Error);
    }
  }

}

}

ActionScript 3 Flexible Layout Class

ActionScript 3 NpFlexLayout Class

The NpFlexLayout Class is designed to simplify aligning DisplayObjects to stage dimensions and responding to changes in stage dimensions at run time by a user or between different users.

This supersedes a simpler implementation of this approach, called “ActionScript 3 Liquid Layout Manager”, which I’ll leave on the site.

As opposed to the LayoutManager class, the NpFlexLayout class is an actual layout manager, taking care of instantiating, destroying and passing on requests to instances of an internal class called NpFLexLayoutItem which wraps each displayObject passed to the NpFlexLayout Class.

Class Features

  • Position objects (x,y) on the stage as a proportion of the stages dimensions
  • offSet each displayObject (x,y) from where it would be by an absolute pixel value
  • set objects to stretch to fit either stage width / height or a proportion of stage dimensions
  • per object minX & minY settings where reflowing, resizing will cease
  • use either the clips registration point, or its visual center (width/height)
  • pause, resume displayObjects responding to resize events
  • update individual class properties at runtime
  • tween objects properties at runtime (uses internal simple tween method)
  • set tween easing
  • add, remove displayObjects from class

View Example
Here is an example; http://www.noponies.com/dev/as3_flexlayout/

Source Files
Here are the relevant source file; http://www.blog.noponies.com/wp-content/uploads/npflexlayout.zip

Dependencies
None.

Class ActionScript

/* AS3
*Copyright 2008 noponies.
*/


package noponies.ui{

  import flash.display.DisplayObject;
  import flash.display.Stage;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.utils.getTimer;
  import flash.utils.Dictionary;
  import noponies.ui.NpFlexLayoutItem;

  /**
  *   <strong>NpFlexLayout</strong> 
  * <br />
  * The NpFlexLayout class is a Display class that repositions objects passed to it when a stage resize event occurs.
  * <p>The class has many parameters for creating flexible layouts. You are able to add and remove clips at runtime, specify the percentage of the stages height
  * and width a clip reflows to. Class supports an offset value in pixels, for offsetting a clips position, relative to where it would have been positioned.
  * Specify a minimum x and y position at which point the clip will no longer be reflowed. Clips can use either their registration point
  * or you can specifiy them to position themselves based off their visible (width*.5, height*.5) center. Clip also supports scaling objects to a stage dimension along with positioning
  * clips. Useful for creating footers etc. With the minimum parameters (displayObject, {x, y}), the class defaults to using a "fast path" to improve performance.
  * <br /><br />
  * <b>Author:</b> noponies - <a href="http://www.blog.noponies.com/" target="_blank">www.blog.noponies.com</a><br />
  *   <b>Class version:</b> 1<br />
  *   <b>Actionscript version:</b> 3.0 Player Version 9.0.28<br />
  *   <b>Copyright:</b>
  *   Creative Commons AttCreative Commons Attribution 3.0 New Zealand License<br />
  *   <a href="http://creativecommons.org/licenses/by/3.0/nz/" target="_blank">http://creativecommons.org/licenses/by/3.0/nz/</a><br />
  *   <em>You can use this class how you like, except as a base for Flash Components or for Flash Template sites.</em><br />
  *   <br />
  *   <b>Date:</b> 07 August 2008<br />
  */


  public class NpFlexLayout extends EventDispatcher {
    //--------------------------------------
    // PRIVATE PROPERTIES
    //--------------------------------------
    private  var stageInstance:Stage;//stage ref
    private var layOutObjects:Dictionary = new Dictionary(true);

   
    /**
    * NpFlexLayout. The constructor will throw an error if either the stages <code>scaleMode</code> is not set to <code>stage.scaleMode = StageScaleMode.NO_SCALE</code> or if the
    * stages <code>align</code> is not set to <code>stage.align = StageAlign.TOP_LEFT</code>. Make sure you set these values in your parent swf!
    * @param stageRef Reference to the stage.
    * @return void
    * @example Flash CS3 Timeline Script Example
    * <listing version="3.0">
    *
    * import noponies.display.NpFlexLayout;
    * stage.scaleMode = StageScaleMode.NO_SCALE;
    * stage.align = StageAlign.TOP_LEFT;
    * //minimal constructor arguments
    * var stageTest:NpFlexLayout = new NpFlexLayout(stage);
    * stageTest.addTarget(blue_mc, {x:0, y:.1,width:1, height:0,offSetY:0})
    *
    * </listing>
    */

    public function NpFlexLayout(stageRef:Stage) {
      stageInstance = stageRef
      //check both scale mode and stage align
      if (stageInstance.scaleMode!="noScale") {
        throw new Error("The Stage scaleMode must be set to \"noScale\". Currently it is set to " +"\""+stageInstance.scaleMode+"\"");
      }
      if (stageInstance.align!="TL") {
        throw new Error("The Stage scaleMode must be set to \"TL\". Currently it is set to " +"\""+stageInstance.align+"\"");
      }
    }
    //--------------------------------------
    // PUBLIC INSTANCE METHODS
    //--------------------------------------
   
    /**
    * The addTarget method is designed to allow add objects to the NpFlexLayout class at runtime. Each addition of a new display object will call the main <code>reflowObjects</code> method, so your object will automatically
    * position itself.
    * <p><strong>Some points to note</strong><br/>
    * <ul><li>Percentage values are in the 0-1 range</li>
    * <li>Optional Params must be wrapped in an Object {} and can appear in any order, or in any amount</li>
    * <li>The target parameter is mandatory</li>
    * <li>The x, y, offSetX, offSetY, width, height, useClipRegPoint, xmin, ymin params are optional</li>
    * <li>Setting width or height to a value other an 0 will enable resizing of your passed DisplayObject</li>
     * </ul><br />
    * <strong>Possible "vars" Object Properties</strong>
     * <ul>
    * <li> x Optional (Default = 0) Number horizontal or x postion on the stage (as a PERCENTAGE of the stage) where you want your object to sit: 0 = left, 1 = full right etc.</li>
    * <li> y Optional (Default = 0) Number vertical or y postion on the stage (as a PERCENTAGE of the stage) where you want your object to sit: 0 = top, 1 = bottom.</li>
    * <li> offSetX Optional (Default = 0) Number representing an horizontal or x offset value, of your clips x position. This value is in pixels. This value can be either negative or positive, depending on what direction you wish to offset your clip.</li>
    * <li> offSetY Optional (Default = 0) Number representing an vertical or y offset value, of your clips y position. This value is in pixels. This value can be either negative or positive, depending on what direction you wish to offset your clip.</li>
    * <li> width Optional (Default = 0) Number representing the width of your object as a (as a PERCENTAGE of the stage width). Setting this to anything other than 0 will make your objects width expand and contract with changes in stage dimensions. </li>
    * <li> height Optional (Default = 0) Number representing the height of your object as a (as a PERCENTAGE of the stage height). Setting this to anything other than 0 will make your objects height expand and contract with changes in stage dimensions.</li>
    * <li> minX Optional (Default = 0) int representing the <strong>minimum x position</strong>  you want your clip to move to, after which point, it will not be adjusted. This is a pixel value!</li>
    * <li> minY Optional (Default = 0) int representing the <strong>minimum y  position</strong> width you want your clip to move to, after which point, it will not be adjusted. This is a pixel value!</li>
    * <li> useClipRegPoint Optional (Default = false) Boolean <code>true</code> means clip is positioned off its reg point, <code>false</code> means its natural center (height/width) is used.</li>
    * </ul>
    * @param target DisplayObject you would like to add to the NpFlexLayout classes influence.
    * @param vars Object containing the various properties used to resposition and align your DisplayObject.
    * @example Various options demonstrated.
    * <listing version="3.0">
    *
    * import noponies.display.NpFlexLayout;
    * stage.scaleMode = StageScaleMode.NO_SCALE;
    * stage.align = StageAlign.TOP_LEFT;
    * //Create class instance
    * var stageTest:NpFlexLayout = new NpFlexLayout(stage);
    * stageTest.addTarget(blue_mc, {x:.5, y:.5});
    * //scale a clip across stage dimensions
    * stageTest.addTarget(green_mc, {x:5, y:.5,offSetX:-100, useClipRegPoint:true, xmin:500});
    *
    * stageTest.addTarget(black,  {x:0, y:.1,width:1, height:0,offSetY:0});
    *
    * </listing>
    * @return void
     */


    public function addTarget(target:DisplayObject, vars:Object):void {
      //create default Object template
      var targObj:Object = {x:0, y:0, useClipRegPoint:false, offSetX:0, offSetY:0, width:0, height:0, minX:0, minY:0}
      //populate object template with passed in params
      for (var i:Object in vars) {
        targObj[i] = vars[i];
      }
      //add elements to dictionary object and instantiate internal NpFlexLayoutItem Class
      layOutObjects[target]new NpFlexLayoutItem(target, targObj)
    }
   
    //--------------------------------------
    // PAUSE REFLOW METHOD
    //--------------------------------------   
    /**
    * The pauseReflowTarget method is designed to allow you temporarily remove objects from the NpFlexLayout classes influence at runtime.
    * <p>The effect of running this method is that the targeted DisplayObject will remove its Event.RESIZE listener. This method is useful if
    * you simply need to remove an disable an object from responding to stage resize events for a short time.</p>
    * @param target DisplayObject you would like to remove from the NpFlexLayout classes influence.
    * @see #resumeReflowTarget() 
    * @see #getReflowStatus()  
    * @return void
    * @example Demo of pauseReflowTarget method.
    * <listing version="3.0">
    *
    * import noponies.display.NpFlexLayout;
    * stage.scaleMode = StageScaleMode.NO_SCALE;
    * stage.align = StageAlign.TOP_LEFT;
    * import gs.TweenLite;
    * //Create class instance
    * var stageTest:NpFlexLayout = new NpFlexLayout(stage);
    * stageTest.addTarget(blue_mc, .5, 0,{useClipRegPoint:true});
    *
    * stage.addEventListener(MouseEvent.CLICK, disableResize)
    * function disableResize(event:MouseEvent):void{
    *   stageTest.pauseReflowTarget(blue_mc)
    * }
    * </listing>
    */

   
    public function pauseReflowTarget(target:DisplayObject):void {
      try {
        layOutObjects[target].disableStageResize()
      }
        catch(e:Error) {
                trace("Problem disabling object " +"\""+target+"\" from responding to Stage Resize Events. Its probably already deleted, or does not exist! "+ e);
      }
     
      }
   
    //--------------------------------------
    // RESUME REFLOW METHOD
    //--------------------------------------   
    /**
    * The resumeReflowTarget method is designed to allow you re enable objects that have been temporarily removed from the NpFlexLayout classes influence at runtime.
    * <p>The effect of running this method is that the targeted DisplayObject will re add its Event.RESIZE listener if has been removed. This method is useful if
    * you have temporarily disabled an object from listening for Event.RESIZE events, and you need to re enable listening for that event. </p>
    * @param target DisplayObject you would like to enable back into the the NpFlexLayout classes influence.
    * @see #pauseReflowTarget()
    * @see #getReflowStatus()    
    * @return void
    * @example Demo of resumeReflowTarget method.
    * <listing version="3.0">
    *
    * import noponies.display.NpFlexLayout;
    * stage.scaleMode = StageScaleMode.NO_SCALE;
    * stage.align = StageAlign.TOP_LEFT;
    * import gs.TweenLite;
    * //Create class instance
    * var stageTest:NpFlexLayout = new NpFlexLayout(stage);
    * stageTest.addTarget(blue_mc, .5, 0,{useClipRegPoint:true});
    *
    * stage.addEventListener(MouseEvent.CLICK, enableResize)
    * function disableResize(event:MouseEvent):void{
    *   stageTest.enableReflowTarget(blue_mc)
    * }
    * </listing>
    */

   
    public function resumeReflowTarget(target:DisplayObject):void {
      try {
        layOutObjects[target].renableStageResize()
      }
        catch(e:Error) {
                trace("Problem re enabling object " +"\""+target+"\" responding to Stage Resize Events. Its probably already deleted, or does not exist! "+ e);
      }
     
      }
   
    //--------------------------------------
    // GET PAUSED STATUS METHOD
    //--------------------------------------   
    /**
    * The getReflowStatus method is designed to allow you to check the reflowStatus of any DisplayObject under the influence of the NpFlexLayout Class.
    * <p>A value of <code>true</code> indicates that a DisplayObject is listening for Event.RESIZE events. A value of <code>false</code> indicates that
    * a DisplayObject is not listening for this event.</p>
    * @param target DisplayObject you would like to test the reflowStatus of
    * @see #resumeReflowTarget()
    * @see #pauseReflowTarget()    
    * @return Boolean
    * @example Demo of getReflowStatus method.
    * <listing version="3.0">
    *
    * import noponies.display.NpFlexLayout;
    * stage.scaleMode = StageScaleMode.NO_SCALE;
    * stage.align = StageAlign.TOP_LEFT;
    * import gs.TweenLite;
    * //Create class instance
    * var stageTest:NpFlexLayout = new NpFlexLayout(stage);
    * stageTest.addTarget(blue_mc, .5, 0,{useClipRegPoint:true});
    *
    * stage.addEventListener(MouseEvent.CLICK, checkReflowStatus)
    * function checkReflowStatus(event:MouseEvent):void{
    *   stageTest.getReflowStatus(blue_mc)
    * }
    * </listing>
    */

   
    public function getReflowStatus(target:DisplayObject):Boolean {
        return layOutObjects[target].reflowing
      }
   
    //--------------------------------------
    // REMOVE TARGET METHOD
    //--------------------------------------   
    /**
    * The removeTarget method is designed to allow you totally remove objects from the NpFlexLayout classes influence at runtime.
    * <p>Calling this method is functionally the same as each object controlled by the NpFlexLayout Class recieving a Event.REMOVED_FROM_STAGE
    * event. The reference to the DisplayObject is also deleted from the internal Dictionary that catalogues all DisplayObjects under the
    * influence of the NpFlexLayout Class. This method is a final concrete <em>delete object from class</em> method. To temporarily disable DisplayObjects
    * from the class use the <code>pauseReflowTarget</code> and the <code>resumeReflowTarget</code> methods.</p>
    * @param target DisplayObject you would like to remove from the NpFlexLayout classes influence.
    * @throw TypeError indicating that you passed an invalid DisplayObject to the NpFlexLayoutClass
    * @return void
    * @example Demo of removeTarget method.
    * <listing version="3.0">
    *
    * import noponies.display.NpFlexLayout;
    * stage.scaleMode = StageScaleMode.NO_SCALE;
    * stage.align = StageAlign.TOP_LEFT;
    * import gs.TweenLite;
    * //Create class instance
    * var stageTest:NpFlexLayout = new NpFlexLayout(stage);
    * stageTest.addTarget(blue_mc, .5, 0,{useClipRegPoint:true});
    *
    * stage.addEventListener(MouseEvent.CLICK, removeObject)
    * function removeObject(event:MouseEvent):void{
    *   stageTest.removeTarget(blue_mc)
    * }
    * </listing>
    */

   
    public