diff --git a/src/BitmapScrollerApp.as b/src/BitmapScrollerApp.as index 673b84d..e014235 100644 --- a/src/BitmapScrollerApp.as +++ b/src/BitmapScrollerApp.as @@ -28,6 +28,7 @@ package { import com.flashartofwar.BitmapScroller; import com.flashartofwar.behaviors.EaseScrollBehavior; + import com.flashartofwar.scroller.HorizontalScrollerLayout; import com.flashartofwar.ui.Slider; import flash.display.Bitmap; @@ -50,7 +51,7 @@ package public class BitmapScrollerApp extends Sprite { - private var preloadList:Array = ["image1.jpg","image2.jpg","image3.jpg","image4.jpg","image5.jpg","image6.jpg","image7.jpg","image8.jpg","image9.jpg","image10.jpg","image11.jpg","image12.jpg","image13.jpg","image14.jpg","image15.jpg","image16.jpg","image17.jpg","image18.jpg","image19.jpg","image20.jpg","image21.jpg","image22.jpg","image23.jpg","image24.jpg","image25.jpg","image26.jpg","image27.jpg","image28.jpg","image29.jpg"]; + private var preloadList:Array = ["image1.jpg","image2.jpg","image3.jpg","image4.jpg","image5.jpg","image6.jpg","image7.jpg","image8.jpg","image9.jpg","image10.jpg","image11.jpg","image12.jpg","image13.jpg","image14.jpg","image15.jpg","image16.jpg","image17.jpg","image18.jpg","image19.jpg","image20.jpg","image21.jpg","image22.jpg","image23.jpg"]; private var baseURL:String = "images/"; private var currentlyLoading:String; private var loader:Loader = new Loader(); @@ -204,6 +205,7 @@ package { bitmapScroller = new BitmapScroller(); + bitmapScroller.layout = new HorizontalScrollerLayout(); bitmapScroller.bitmapDataCollection = images; addChild(bitmapScroller); bitmapScroller.width = stage.stageWidth; diff --git a/src/com/flashartofwar/BitmapScroller.as b/src/com/flashartofwar/BitmapScroller.as index 3fe628e..1f35182 100644 --- a/src/com/flashartofwar/BitmapScroller.as +++ b/src/com/flashartofwar/BitmapScroller.as @@ -22,337 +22,265 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. - * + * */ package com.flashartofwar { - import flash.display.Bitmap; - import flash.display.BitmapData; - import flash.events.Event; - import flash.geom.Point; - import flash.geom.Rectangle; - - public class BitmapScroller extends Bitmap - { - protected const INVALID_SIZE:String = "size"; - protected const INVALID_SCROLL:String = "scroll"; - protected const INVALID_SIZE_SCROLL:String = "all"; - protected const INVALID_VISUALS:String = "visuals"; - - protected var _bitmapDataCollection:Array; - protected var collectionRects:Array = []; - protected var _totalWidth:int = 0; - protected var _maxHeight:Number = 0; - protected var collectionTotal:int = 0; - protected var copyPixelOffset:Point = new Point(); - protected var internalSampleArea:Rectangle = new Rectangle(0, 0, 0, 0); - protected var collectionID:int; - protected var sourceRect:Rectangle; - protected var sourceBitmapData:BitmapData; - protected var leftOver:Number; - protected var point:Point; - protected var calculationPoint:Point = new Point(); - protected var difference:Number; - protected var sampleArea:Rectangle; - protected var samplePositionPoint:Point = new Point(); - protected var _invalid:Boolean; - protected var invalidSize:Boolean; - protected var invalidScroll:Boolean; - protected var invalidVisuals:Boolean; - - - public function BitmapScroller(bitmapData:BitmapData = null, pixelSnapping:String = "auto", smoothing:Boolean = false) - { - super(null, pixelSnapping, smoothing); - addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); - } - - override public function get width():Number - { - return internalSampleArea.width; - } - - override public function set width(value:Number):void - { - if (value == internalSampleArea.width) - { - return; - } - else - { - internalSampleArea.width = value; - invalidate(INVALID_SIZE); - } - } - - override public function get height():Number - { - return internalSampleArea.height; - } - - override public function set height(value:Number):void - { - if (value == internalSampleArea.height) - { - return; - } - else - { - internalSampleArea.height = value; - invalidate(INVALID_SIZE); - } - } - - public function get scrollX():Number - { - return internalSampleArea.x; - } - - public function set scrollX(value:Number):void - { - if (value == internalSampleArea.x) - { - return; - } - else - { - internalSampleArea.x = value; - invalidate(INVALID_SCROLL); - } - } - - public function get totalWidth():int - { - return _totalWidth; - } - - public function get maxHeight():Number - { - return _maxHeight; - } - - public function render():void - { - - if (_invalid) - { - // We check to see if the size has changed. If it has we create a new bitmap. If not we clear it with fillRect - if (invalidSize) - { - bitmapData = new BitmapData(internalSampleArea.width, internalSampleArea.height, true, 0x000000); - } - else - { - bitmapData.fillRect(internalSampleArea, 0); - } - - // Call sample to get the party started - draw(internalSampleArea.clone()); - - // Clear any invalidation - clearInvalidation(); - } - } - - private function clearInvalidation():void - { - _invalid = false; - invalidSize = false; - invalidScroll = false; - invalidVisuals = false; - } - - protected function indexCollection():void - { - var i:int; - collectionTotal = _bitmapDataCollection.length; - _totalWidth = 0; - _maxHeight = 0; - - for (i = 0; i < collectionTotal; ++ i) - { - if (_bitmapDataCollection[i] is BitmapData) - { - indexBitmapData(_bitmapDataCollection[i]); - } - else - { - throw new Error("BitmapScroller can only process BitmapData."); - } - } - - } - - protected function indexBitmapData(bmd:BitmapData):void - { - var lastRect:Rectangle = collectionRects[collectionRects.length - 1]; - - var lastX:int = lastRect ? lastRect.x + lastRect.width + 1 : 0; - - // create a rect to represent the BitmapData - var rect:Rectangle = new Rectangle(lastX, 0, bmd.width, bmd.height); - - collectionRects.push(rect); - - _totalWidth += rect.width; - - if (bmd.height > _maxHeight) - { - _maxHeight = bmd.height; - } - - } - - /** - * - * @param sampleCoord - * @return - */ - protected function calculateCollectionStartIndex(sampleCoord:Point):int - { - - if (sampleCoord.x < 0) - return -1; - - var i:int; - - for (i = 0; i < collectionTotal; ++ i) - { - if (collectionRects[i].containsPoint(sampleCoord)) - { - return i; - } - } - - return -1; - } - - protected function draw(sampleArea:Rectangle, offset:Point = null):void - { - calculationPoint.x = sampleArea.x; - calculationPoint.y = sampleArea.y; - - collectionID = calculateCollectionStartIndex(calculationPoint); - - if (collectionID != -1) - { - sourceRect = collectionRects[collectionID]; - - sourceBitmapData = _bitmapDataCollection[collectionID]; - - leftOver = Math.round(calculateLeftOverValue(sampleArea.x, sampleArea.width, sourceRect)); - - if (!offset) - offset = copyPixelOffset; - - point = calculateSamplePosition(sampleArea, sourceRect); - - sampleArea.x = point.x; - sampleArea.y = point.y; - - bitmapData.copyPixels(sourceBitmapData, sampleArea, offset); - - if (leftOver > 0) - { - offset = new Point(bitmapData.width - leftOver, 0); - var leftOverSampleArea:Rectangle = calculateLeftOverSampleArea(sampleArea, leftOver, sourceRect); - - draw(leftOverSampleArea, offset); - } - - } - } - - - protected function calculateLeftOverValue(offset:Number, sampleWidth:Number, sourceRect:Rectangle):Number - { - - difference = (offset + sampleWidth) - (sourceRect.x + sourceRect.width); - - return (difference < 0) ? 0 : difference; - } - - - protected function calculateLeftoverOffset(sampleArea:Rectangle, leftOver:Number):Point - { - - return new Point(sampleArea.width - leftOver, 0); - } - - protected function calculateLeftOverSampleArea(sampleAreaSRC:Rectangle, leftOver:Number, sourceRect:Rectangle):Rectangle - { - sampleArea = sampleAreaSRC.clone(); - sampleArea.width = leftOver + 1; - sampleArea.x = sourceRect.x + sourceRect.width + 1; - - return sampleArea; - } - - protected function calculateSamplePosition(sampleRect:Rectangle, sourceArea:Rectangle):Point - { - samplePositionPoint.x = sampleRect.x - sourceArea.x; - - return samplePositionPoint; - } - - public function clear():void - { - - } - - /** Invalidation Logic **/ - - protected function invalidate(type:String = "all"):void - { - if (!_invalid) - { - _invalid = true; - - switch (type) - { - case INVALID_VISUALS: - invalidVisuals = true; - break; - case INVALID_SIZE: - invalidSize = true; - break; - case INVALID_SCROLL: - invalidScroll = true; - break; - case INVALID_SIZE_SCROLL: - invalidScroll = true; - invalidSize = true; - break; - } - } - } - - /** - * - * @param event - */ - protected function onAddedToStage(event:Event):void - { - removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); - addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); - render(); - } - - /** - * - * @param event - */ - protected function onRemovedFromStage(event:Event):void - { - removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); - removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); - } - - public function set bitmapDataCollection(value:Array):void - { - _bitmapDataCollection = value; - indexCollection(); - } - } + import com.flashartofwar.scroller.IScrollerLayout; + import com.flashartofwar.scroller.VerticalScrollerLayout; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.events.Event; + import flash.geom.Point; + import flash.geom.Rectangle; + + /** + * BitmapScroller v1.1 + * @author Paul Taylor, guyinthechair.com + * + */ + public class BitmapScroller extends Bitmap + { + public function BitmapScroller(pixelSnapping:String = "auto", smoothing:Boolean = false) + { + super(null, pixelSnapping, smoothing); + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + } + + /** + * Tells BitmapScroller to force the Flash Player to cache the + * pixels of each bitmap when the bitmap is first indexed. + * + * The increased rendering time of large bitmaps will cause the + * Flash Player to extend the rendering phase of the frame's + * life, causing a visible hang when the bitmaps are first + * scrolled. + * + * If this is set to true, BitmapScroller will force the + * Flash Player to cache the pixels of each bitmap when it is + * first introduced. This increases bitmap indexing time, + * but ensures that the first scroll is as fast as the rest. + * + * @default false + */ + public var cacheOnIndex:Boolean = false; + + private var area:Rectangle = new Rectangle(0, 0, 1, 1); + + private var sizeChanged:Boolean = false; + private var invalidated:Boolean = false; + + override public function get width():Number + { + return area.width; + } + + override public function set width(value:Number):void + { + if(value == area.width) + return; + + area.width = value; + sizeChanged = true; + invalidated = true; + } + + override public function get height():Number + { + return area.height; + } + + override public function set height(value:Number):void + { + if(value == area.height) + return; + + area.height = value; + sizeChanged = true; + invalidated = true; + } + + override public function set scrollRect(value:Rectangle):void + { + area.x = value.x; + area.y = value.y; + width = value.width; + height = value.height; + invalidated = true; + } + + override public function get scrollRect():Rectangle + { + return area.clone(); + } + + public function get scrollX():Number + { + return area.x; + } + + public function set scrollX(value:Number):void + { + if(value == area.x) + return; + + area.x = value; + invalidated = true; + } + + public function get scrollY():Number + { + return area.y; + } + + public function set scrollY(value:Number):void + { + if(value == area.y) + return; + + area.y = value; + invalidated = true; + } + + public function get totalWidth():Number + { + return layout.width; + } + + public function get totalHeight():Number + { + return layout.height; + } + + public function render():void + { + if(!invalidated) + return; + + if(sizeChanged || !bitmapData) + initBitmapData(); + else + bitmapData.fillRect(area, 0); + + sizeChanged = false; + + draw(); + invalidated = false; + } + + public function get layout():IScrollerLayout + { + return _layout ||= new VerticalScrollerLayout(); + } + + private var _layout:IScrollerLayout; + + public function set layout(value:IScrollerLayout):void + { + if(value == _layout) + return; + + _layout = value; + indexCollection(); + } + + private var _bitmapDataCollection:Array; + + public function get bitmapDataCollection():Array + { + return _bitmapDataCollection; + } + + public function set bitmapDataCollection(value:Array):void + { + if(value == _bitmapDataCollection) + return; + + _bitmapDataCollection = value; + invalidated = true; + indexCollection(); + } + + protected function indexCollection():void + { + if(!bitmapDataCollection) + return; + + var n:int = bitmapDataCollection.length; + var val:Object; + + for(var i:int = 0; i < n; i += 1) + { + val = bitmapDataCollection[i]; + + if(val is BitmapData) + indexBitmapData(BitmapData(val)); + else + throw new Error("BitmapScroller can only process BitmapData."); + } + } + + protected function indexBitmapData(bmp:BitmapData):void + { + layout.position(bmp); + + if(!cacheOnIndex) + return; + + if(!bitmapData) + initBitmapData(); + + bitmapData.copyPixels(bmp, bmp.rect.clone(), new Point()); + } + + protected function initBitmapData():void + { + bitmapData = new BitmapData(area.width, area.height, true, 0); + } + + protected function draw():void + { + var theArea:Rectangle = area.clone(); + var bitmaps:Array = layout.getVisibleBitmaps(theArea); + var n:int = bitmaps.length; + + var bmd:BitmapData; + var rect:Rectangle; + var offset:Point = new Point(); + + for(var i:int = 0; i < n; i += 1) + { + bmd = bitmaps[i]; + + rect = layout.getRect(bmd, theArea); + + bitmapData.copyPixels(bmd, rect, offset); + + offset = layout.adjustOffset(rect, offset); + } + } + + /** + * + * @param event + */ + protected function onAddedToStage(event:Event):void + { + removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); + render(); + } + + /** + * + * @param event + */ + protected function onRemovedFromStage(event:Event):void + { + removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); + removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + } + } } \ No newline at end of file diff --git a/src/com/flashartofwar/scroller/HorizontalScrollerLayout.as b/src/com/flashartofwar/scroller/HorizontalScrollerLayout.as new file mode 100644 index 0000000..94044bb --- /dev/null +++ b/src/com/flashartofwar/scroller/HorizontalScrollerLayout.as @@ -0,0 +1,65 @@ +package com.flashartofwar.scroller +{ + import flash.display.BitmapData; + import flash.geom.Point; + import flash.geom.Rectangle; + + public final class HorizontalScrollerLayout extends ScrollerLayout + { + public function HorizontalScrollerLayout() + { + bitmaps = new SparseArray(); + } + + override public function clear():void + { + bitmaps = new SparseArray(); + } + + override public function position(bmp:BitmapData):void + { + if(bmp.height > largestHeight) + largestHeight = bmp.height; + + SparseArray(bitmaps).add(bmp, bmp.width); + } + + override public function getRect(bmd:BitmapData, area:Rectangle):Rectangle + { + var rect:Rectangle = super.getRect(bmd, area); + + var position:int = SparseArray(bitmaps).getItemPosition(bmd); + + if(area.x > position) + { + rect.x = area.x - position; + rect.width -= (area.x - position); + } + + return rect; + } + + override public function adjustOffset(rect:Rectangle, offset:Point):Point + { + offset.x += rect.width; + return offset; + } + + override public function getVisibleBitmaps(visibleArea:Rectangle):Array + { + return SparseArray(bitmaps).getRange(visibleArea.x, visibleArea.x + visibleArea.width); + } + + private var largestHeight:Number = 0; + + override public function get height():Number + { + return largestHeight; + } + + override public function get width():Number + { + return SparseArray(bitmaps).size; + } + } +} \ No newline at end of file diff --git a/src/com/flashartofwar/scroller/IScrollerLayout.as b/src/com/flashartofwar/scroller/IScrollerLayout.as new file mode 100644 index 0000000..f8bccec --- /dev/null +++ b/src/com/flashartofwar/scroller/IScrollerLayout.as @@ -0,0 +1,20 @@ +package com.flashartofwar.scroller +{ + import flash.display.BitmapData; + import flash.geom.Point; + import flash.geom.Rectangle; + + public interface IScrollerLayout + { + function clear():void; + function position(bmd:BitmapData):void; + + function getVisibleBitmaps(visibleArea:Rectangle):Array; + + function getRect(bmd:BitmapData, area:Rectangle):Rectangle; + function adjustOffset(rect:Rectangle, offset:Point):Point; + + function get height():Number; + function get width():Number; + } +} \ No newline at end of file diff --git a/src/com/flashartofwar/scroller/ScrollerLayout.as b/src/com/flashartofwar/scroller/ScrollerLayout.as new file mode 100644 index 0000000..f34684a --- /dev/null +++ b/src/com/flashartofwar/scroller/ScrollerLayout.as @@ -0,0 +1,46 @@ +package com.flashartofwar.scroller +{ + import flash.display.BitmapData; + import flash.geom.Point; + import flash.geom.Rectangle; + + public class ScrollerLayout implements IScrollerLayout + { + public function clear():void + { + bitmaps = []; + } + + protected var bitmaps:Array = []; + + public function getRect(bmd:BitmapData, area:Rectangle):Rectangle + { + return bmd.rect.clone(); + } + + public function adjustOffset(rect:Rectangle, offset:Point):Point + { + return offset; + } + + public function getVisibleBitmaps(visibleArea:Rectangle):Array + { + return bitmaps; + } + + public function position(bmd:BitmapData):void + { + bitmaps.push(bmd); + } + + public function get height():Number + { + return 0; + } + + public function get width():Number + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/com/flashartofwar/scroller/SparseArray.as b/src/com/flashartofwar/scroller/SparseArray.as new file mode 100644 index 0000000..e600f2f --- /dev/null +++ b/src/com/flashartofwar/scroller/SparseArray.as @@ -0,0 +1,70 @@ +package com.flashartofwar.scroller +{ + import flash.utils.Dictionary; + + public dynamic class SparseArray extends Array + { + private var positionMap:Dictionary = new Dictionary(false); + private var _size:Number = 0; + + public function add(item:*, size:int):* + { + positionMap[_size] = length; + positionMap[item] = _size; + push(item); + _size += size; + } + + public function get size():Number + { + return _size; + } + + public function clear():void + { + positionMap = new Dictionary(false); + length = 0; + _size = 0; + } + + public function getItemPosition(item:*):int + { + if(!hasItem(item)) + return -1; + + return positionMap[item]; + } + + public function hasItem(item:*):Boolean + { + return item in positionMap; + } + + public function getRange(start:int, end:int):Array + { + var a:Array = []; + + start = getPositionIndex(start); + end = Math.min(getPositionIndex(end) + 1, length); + + for(; start < end; start += 1) + { + a.push(this[start]); + } + + return a; + } + + private function getPositionIndex(position:int):int + { + while((position in positionMap) == false) + { + position -= 1; + if(position < 0) + return 0; + } + + return positionMap[position]; + } + } +} \ No newline at end of file diff --git a/src/com/flashartofwar/scroller/VerticalScrollerLayout.as b/src/com/flashartofwar/scroller/VerticalScrollerLayout.as new file mode 100644 index 0000000..046047f --- /dev/null +++ b/src/com/flashartofwar/scroller/VerticalScrollerLayout.as @@ -0,0 +1,65 @@ +package com.flashartofwar.scroller +{ + import flash.display.BitmapData; + import flash.geom.Point; + import flash.geom.Rectangle; + + public final class VerticalScrollerLayout extends ScrollerLayout + { + public function VerticalScrollerLayout() + { + bitmaps = new SparseArray(); + } + + override public function clear():void + { + bitmaps = new SparseArray(); + } + + override public function position(bmp:BitmapData):void + { + if(bmp.width > largestWidth) + largestWidth = bmp.width; + + SparseArray(bitmaps).add(bmp, bmp.height); + } + + override public function getRect(bmd:BitmapData, area:Rectangle):Rectangle + { + var rect:Rectangle = super.getRect(bmd, area); + + var position:int = SparseArray(bitmaps).getItemPosition(bmd); + + if(area.y > position) + { + rect.y = area.y - position; + rect.height -= (area.y - position); + } + + return rect; + } + + override public function adjustOffset(rect:Rectangle, offset:Point):Point + { + offset.y += rect.height; + return offset; + } + + override public function getVisibleBitmaps(visibleArea:Rectangle):Array + { + return SparseArray(bitmaps).getRange(visibleArea.y, visibleArea.y + visibleArea.height); + } + + private var largestWidth:Number = 0; + + override public function get width():Number + { + return largestWidth; + } + + override public function get height():Number + { + return SparseArray(bitmaps).size; + } + } +} \ No newline at end of file