fix shared sprite size through internal cache

Strict

Private

Import bono
Import mojo.graphics

Public

Class Sprite Extends BaseDisplayObject Implements Updateable, Renderable
    Private

    Field currentFrame:Int
    Field frameCount:Int
    Field frameSize:Vector2D
    Field frameTimer:Int
    Field image:Image
    Field renderPos:Vector2D = New Vector2D()
    Field imageName:String
    Field baseRotation:Int
    Field _scale:Vector2D = New Vector2D(1, 1)
    Field lastScale:Vector2D = New Vector2D(1, 1)
    Global cacheImage:StringMap<Image> = New StringMap<Image>()
    Global cacheSize:StringMap<Vector2D> = New StringMap<Vector2D>()

    Public

    Field valign:Int = Align.TOP
    Field halign:Int = Align.LEFT
    Field frameSpeed:Int
    Field loopAnimation:Bool
    Field rotation:Float

    Method New(imageName:String, pos:Vector2D=Null)
        Self.imageName = imageName

        LoadImage()
    End

    Method New(imageName:String, frameSize:Vector2D, frameCount:Int, frameSpeed:Int, pos:Vector2D=Null)
        Self.imageName = imageName
        Self.frameSize = frameSize
        Self.frameCount = frameCount
        Self.frameSpeed = frameSpeed

        LoadImage()
    End

    Method New(imageName:String, image:Image, forcedSize:Vector2D = Null, baseRotation:Int = 0)
        Self.imageName = imageName
        Self.image = image
        Self.baseRotation = baseRotation

        If forcedSize
            SetSize(forcedSize)
        Else
            SetSize(New Vector2D(image.Width(), image.Height()))
        End
    End

    Method GrabSprite:Sprite(name:String, src:Vector2D, size:Vector2D, rotation:Int = 0)
        Local img:Image = image.GrabImage(
            src.x, src.y,
            size.x, size.y,
            1,
            Image.MidHandle)

        Local forcedSize:Vector2D
        Select rotation
        Case 90, 270, -90, -270
            forcedSize = New Vector2D(size.y, size.x)
        Case 0, 180, -180
            ' nothing to do here
        Default
            Throw New InvalidArgumentException(
                "Unable to handle given rotation '" + String(rotation) + "'")
        End

        Return New Sprite(name, img, forcedSize, rotation)
    End

    Method Collide:Bool(checkPos:Vector2D)
        Local offset:Vector2D = New Vector2D()
        Align.Horizontal(offset, Self, halign)
        Align.Vertical(offset, Self, valign)

        Return Super.Collide(checkPos.Copy().Sub(offset))
    End

    Method OnRender:Void()
        GetSize().Div(lastScale).Mul(_scale)
        lastScale.Set(_scale)

        renderPos.Set(GetCenter())
        renderPos.Add(GetPosition())
        Align.Horizontal(renderPos, Self, halign)
        Align.Vertical(renderPos, Self, valign)

        GetColor().Activate()
        DrawImage(
            image,
            renderPos.x, renderPos.y,
            baseRotation + rotation,
            _scale.x, _scale.y,
            currentFrame)
        GetColor().Deactivate()
    End

    Method OnUpdate:Void(deltaTimer:DeltaTimer)
        If frameCount <= 0 Then Return
        If animationIsDone Then Return

        If frameTimer < frameSpeed
            frameTimer += deltaTimer.frameTime
            Return
        End

        If (currentFrame + 1) = frameCount
            If loopAnimation
                currentFrame = 1
            End
        Else
            currentFrame += 1
        End
        frameTimer = 0
    End

    Method Restart:Void()
        currentFrame = 0
    End

    Method animationIsDone:Bool() Property
        If loopAnimation Then Return False
        Return (currentFrame = frameCount)
    End

    Method scale:Vector2D() Property
        Return _scale
    End

    Method scale:Void(newScale:Vector2D) Property
        _scale = newScale
    End

    Method filepath:String() Property
        Return imageName
    End

    Method DrawImageRect:Void(pos:Vector2D, rectPos:Vector2D, rectSize:Vector2D)
        DrawImageRect(pos.x, pos.y, rectPos.x, rectPos.y, rectSize.x, rectSize.y)
    End

    Method DrawImageRect:Void(x:Float, y:Float, srcX:Float, srcY:Float, srcWidth:Float, srcHeight:Float)
        x += GetCenter().x
        y += GetCenter().y
        graphics.DrawImageRect(image, x, y, srcX, srcY, srcWidth, srcHeight, rotation, _scale.x, _scale.y, currentFrame)
    End

    Private

    Method CacheSet:Void(name:String, image:Image, size:Vector2D)
        cacheImage.Set(name, image)
        cacheSize.Set(name, size.Copy())
    End

    Method CacheGetSize:Vector2D(name:String)
        Return cacheSize.Get(name).Copy()
    End

    Method CacheGetImage:Image(name:String)
        Return cacheImage.Get(name)
    End

    Method LoadImage:Void()
        ' Try the cache first ...
        image = CacheGetImage(imageName)
        If image
            SetSize(CacheGetSize(imageName))
        End

        ' ... but handle misses aswell
        If Not image
            If frameSize
                image = graphics.LoadImage(imageName, frameSize.x, frameSize.y, frameCount, Image.MidHandle)
                FailIfImageNotLoaded()
                SetSize(frameSize.Copy())
            Else
                image = graphics.LoadImage(imageName, 1, Image.MidHandle)
                FailIfImageNotLoaded()
                SetSize(New Vector2D(image.Width(), image.Height()))
            End
            CacheSet(imageName, image, GetSize())
        End

        GetSize().Mul(scale)
    End

    Method FailIfImageNotLoaded:Void()
        If Not image Then Throw New RuntimeException(
            "Unable to load image file: " + imageName)
    End
End

Strict

Private

Import bono
Import mojo.graphics

Public

Class Sprite Extends BaseDisplayObject Implements Updateable, Renderable
    Private

    Field currentFrame:Int
    Field frameCount:Int
    Field frameSize:Vector2D
    Field frameTimer:Int
    Field image:Image
    Field renderPos:Vector2D = New Vector2D()
    Field imageName:String
    Field baseRotation:Int
    Field _scale:Vector2D = New Vector2D(1, 1)
    Field lastScale:Vector2D = New Vector2D(1, 1)
    Global cacheImage:StringMap<Image> = New StringMap<Image>()
    Global cacheSize:StringMap<Vector2D> = New StringMap<Vector2D>()

    Public

    Field valign:Int = Align.TOP
    Field halign:Int = Align.LEFT
    Field frameSpeed:Int
    Field loopAnimation:Bool
    Field rotation:Float

    Method New(imageName:String, pos:Vector2D=Null)
        Self.imageName = imageName

        LoadImage()
    End

    Method New(imageName:String, frameSize:Vector2D, frameCount:Int, frameSpeed:Int, pos:Vector2D=Null)
        Self.imageName = imageName
        Self.frameSize = frameSize
        Self.frameCount = frameCount
        Self.frameSpeed = frameSpeed

        LoadImage()
    End

    Method New(imageName:String, image:Image, forcedSize:Vector2D = Null, baseRotation:Int = 0)
        Self.imageName = imageName
        Self.image = image
        Self.baseRotation = baseRotation

        If forcedSize
            SetSize(forcedSize)
        Else
            SetSize(New Vector2D(image.Width(), image.Height()))
        End
    End

    Method GrabSprite:Sprite(name:String, src:Vector2D, size:Vector2D, rotation:Int = 0)
        Local img:Image = image.GrabImage(
            src.x, src.y,
            size.x, size.y,
            1,
            Image.MidHandle)

        Local forcedSize:Vector2D
        Select rotation
        Case 90, 270, -90, -270
            forcedSize = New Vector2D(size.y, size.x)
        Case 0, 180, -180
            ' nothing to do here
        Default
            Throw New InvalidArgumentException(
                "Unable to handle given rotation '" + String(rotation) + "'")
        End

        Return New Sprite(name, img, forcedSize, rotation)
    End

    Method Collide:Bool(checkPos:Vector2D)
        Local offset:Vector2D = New Vector2D()
        Align.Horizontal(offset, Self, halign)
        Align.Vertical(offset, Self, valign)

        Return Super.Collide(checkPos.Copy().Sub(offset))
    End

    Method OnRender:Void()
        GetSize().Div(lastScale).Mul(_scale)
        lastScale.Set(_scale)

        renderPos.Set(GetCenter())
        renderPos.Add(GetPosition())
        Align.Horizontal(renderPos, Self, halign)
        Align.Vertical(renderPos, Self, valign)

        GetColor().Activate()
        DrawImage(
            image,
            renderPos.x, renderPos.y,
            baseRotation + rotation,
            _scale.x, _scale.y,
            currentFrame)
        GetColor().Deactivate()
    End

    Method OnUpdate:Void(deltaTimer:DeltaTimer)
        If frameCount <= 0 Then Return
        If animationIsDone Then Return

        If frameTimer < frameSpeed
            frameTimer += deltaTimer.frameTime
            Return
        End

        If (currentFrame + 1) = frameCount
            If loopAnimation
                currentFrame = 1
            End
        Else
            currentFrame += 1
        End
        frameTimer = 0
    End

    Method Restart:Void()
        currentFrame = 0
    End

    Method animationIsDone:Bool() Property
        If loopAnimation Then Return False
        Return (currentFrame = frameCount)
    End

    Method scale:Vector2D() Property
        Return _scale
    End

    Method scale:Void(newScale:Vector2D) Property
        _scale = newScale
    End

    Method filepath:String() Property
        Return imageName
    End

    Method DrawImageRect:Void(pos:Vector2D, rectPos:Vector2D, rectSize:Vector2D)
        DrawImageRect(pos.x, pos.y, rectPos.x, rectPos.y, rectSize.x, rectSize.y)
    End

    Method DrawImageRect:Void(x:Float, y:Float, srcX:Float, srcY:Float, srcWidth:Float, srcHeight:Float)
        x += GetCenter().x
        y += GetCenter().y
        graphics.DrawImageRect(image, x, y, srcX, srcY, srcWidth, srcHeight, rotation, _scale.x, _scale.y, currentFrame)
    End

    Private

    Method CacheSet:Void(name:String, image:Image, size:Vector2D)
        cacheImage.Set(name, image)
        cacheSize.Set(name, size)
    End

    Method CacheGetSize:Vector2D(name:String)
        Return cacheSize.Get(name)
    End

    Method CacheGetImage:Image(name:String)
        Return cacheImage.Get(name)
    End

    Method LoadImage:Void()
        ' Try the cache first ...
        image = CacheGetImage(imageName)
        If image
            SetSize(CacheGetSize(imageName))
        End

        ' ... but handle misses aswell
        If Not image
            If frameSize
                image = graphics.LoadImage(imageName, frameSize.x, frameSize.y, frameCount, Image.MidHandle)
                FailIfImageNotLoaded()
                SetSize(frameSize.Copy())
            Else
                image = graphics.LoadImage(imageName, 1, Image.MidHandle)
                FailIfImageNotLoaded()
                SetSize(New Vector2D(image.Width(), image.Height()))
            End
            CacheSet(imageName, image, GetSize())
        End

        GetSize().Mul(scale)
    End

    Method FailIfImageNotLoaded:Void()
        If Not image Then Throw New RuntimeException(
            "Unable to load image file: " + imageName)
    End
End


Fixed FlxSprite.AddAnimation

Fixed FlxSprite.AddAnimation

Strict

Import mojo

Import flxextern
Import flxpoint
Import flxobject
Import flxcamera
Import flxg

Import system.flxanim
Import system.flxcolor

Import "data/default_flx.png"

Class FlxSprite Extends FlxObject

	Global ClassObject:Object

	Field origin:FlxPoint

	Field offset:FlxPoint

	Field scale:FlxPoint

	Field blend:Int

	Field finished:Bool

	Field frameWidth:Int

	Field frameHeight:Int

	Field frames:Int

	Field dirty:Bool

	Field _color:FlxColor

	Field _mixedColor:FlxColor

	Field _camera:FlxCamera

Private
	Global _GraphicLoader:FlxGraphicLoader = New FlxGraphicLoader()

	Global _Matrix:Float[6]

	Field _animations:StringMap<FlxAnim>

	Field _flipped:Bool

	Field _flipNeeded:Bool

	Field _curAnim:FlxAnim

	Field _curFrame:Int

	Field _curIndex:Int

	Field _frameTimer:Float

	Field _paused:Bool

	Field _callback:FlxAnimationListener

	Field _facing:Int

	Field _alpha:Float

	Field _bakedRotation:Float

	Field _pixels:Image

	Field _halfWidth:Float

	Field _halfHeight:Float

	Field _renderer:FlxSpriteRenderer

Public
	Method New(x:Float = 0, y:Float = 0, simpleGraphic:String = "")
		Super.New(x, y)

		offset = New FlxPoint()
		origin = New FlxPoint()

		scale = New FlxPoint(1.0, 1.0)
		_alpha = 1
		_color = New FlxColor(FlxG.WHITE)
		blend = AlphaBlend
		_cameras = Null

		finished = False
		_facing = RIGHT
		_animations = New StringMap<FlxAnim>
		_flipped = False
		_curAnim = Null
		_curFrame = 0
		_curIndex = 0
		_frameTimer = 0
		_paused = False

		_callback = Null

		_mixedColor = New FlxColor(FlxG.WHITE)

		If (simpleGraphic.Length() = 0)
			simpleGraphic = "default" + FlxG.DATA_SUFFIX
		End If

		LoadGraphic(simpleGraphic)
	End Method

	Method Destroy:Void()
		If (_animations <> Null) Then
			_animations.Clear()
			_animations = Null
		End If

		offset = Null
		origin = Null
		scale = Null
		_curAnim = Null
		_callback = Null
		_color = Null
		_mixedColor = Null
		_camera = Null
		_renderer = Null

		Super.Destroy()
	End Method

	Method LoadGraphic:FlxSprite(graphic:String, animated:Bool = False, reverse:Bool = False, width:Int = 0, height:Int = 0, unique:Bool = False)
		_bakedRotation = 0

		_GraphicLoader.name = graphic
		_GraphicLoader.animated = animated
		_GraphicLoader.width = width
		_GraphicLoader.height = height

		_pixels = FlxG.AddBitmap(graphic, _GraphicLoader, unique)

		_flipped = reverse

		Self.width = _pixels.Width()
		frameWidth = Self.width

		Self.height = _pixels.Height()
		frameHeight = Self.height

		_ResetHelpers()

		Return Self
	End Method

	Method LoadRotatedGraphic:FlxSprite(graphic:String, rotations:Int = 16, frame:Int = -1)
		Error "LoadRotatedGraphic not currenlty support in " + FlxG.LIBRARY_NAME
		'TODO
		Return Null
	End Method

	Method MakeGraphic:FlxSprite(width:Int, height:Int, color:Int = FlxG.WHITE, unique:Bool = False, key:String = "")
		_pixels = FlxG.CreateBitmap(width, height, color, unique, key)
		_bakedRotation = 0

		Self.width = width
		frameWidth = width
		Self.height = height
		frameHeight = height

		_ResetHelpers()

		Return Self
	End Method

	Method PostUpdate:Void()
		Super.PostUpdate()
		_UpdateAnimation()
	End Method

	Method Draw:Void()
		If (_flickerTimer <> 0) Then
			_flicker = Not _flicker
			If (_flicker) Return
		End If

		_camera = FlxG._CurrentCamera

		If (_cameras <> Null And Not _cameras.Contains(_camera.ID)) Return
		If (Not OnScreen(_camera)) Return

		If (dirty) _CalcFrame()

		If (FlxG._LastDrawingBlend <> blend) Then
			SetBlend(blend)
			FlxG._LastDrawingBlend = blend
		End If

		_point.x = x - int(_camera.scroll.x * scrollFactor.x) - offset.x
		_point.y = y - int(_camera.scroll.y * scrollFactor.y) - offset.y

		If (_point.x > 0) Then
			_point.x += 0.0000001
		Else
			_point.x -= 0.0000001
		End If

		If (_point.y > 0) Then
			_point.y += 0.0000001
		Else
			_point.y -= 0.0000001
		End If

		If (_camera.Color <> FlxG.WHITE) Then
			_mixedColor.MixRGB(_color, _camera._color)

			If (FlxG._LastDrawingColor <> _mixedColor.argb) Then
				SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
				FlxG._LastDrawingColor = _mixedColor.argb
			End If
		Else
			If (FlxG._LastDrawingColor <> _color.argb) Then
				SetColor(_color.r, _color.g, _color.b)
				FlxG._LastDrawingColor = _color.argb
			End If
		End If

		If (_camera.Alpha < 1) Then
			Local _mixedAlpha:Float = _camera.Alpha * _alpha

			If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
				SetAlpha(_mixedAlpha)
				FlxG._LastDrawingAlpha = _mixedAlpha
			End If
		Else
			If (FlxG._LastDrawingAlpha <> _alpha) Then
				SetAlpha(_alpha)
				FlxG._LastDrawingAlpha = _alpha
			End If
		End If

		If ((angle = 0 Or _bakedRotation > 0) And scale.x = 1 And scale.y = 1) Then
			If ( Not _flipNeeded) Then
				If (_renderer = Null) Then
					DrawImage(_pixels, _point.x, _point.y, _curIndex)
				Else
					_renderer.OnSpriteRender(_point.x, _point.y)
				End If
			Else
				PushMatrix()
					Transform(-1, 0, 0, 1, _point.x + _halfWidth, _point.y + _halfHeight)

					If (_renderer = Null) Then
						DrawImage(_pixels, -_halfWidth, -_halfHeight, _curIndex)
					Else
						_renderer.OnSpriteRender(-_halfWidth, -_halfHeight)
					End If
				PopMatrix()
			End If
		Else
			PushMatrix()
				'Translate
				_Matrix[4] = _point.x + origin.x
				_Matrix[5] = _point.y + origin.y

				'Scale
				_Matrix[0] = scale.x
				_Matrix[3] = scale.y

				'Rotate
				If (angle <> 0 And _bakedRotation = 0) Then
					Local sin:Float = Sin(angle)
					Local cos:Float = Cos(angle)

					_Matrix[1] = sin * _Matrix[0]
					_Matrix[2] = -sin * _Matrix[3]
					_Matrix[0] *= cos
					_Matrix[3] *= cos
				Else
					_Matrix[1] = 0
					_Matrix[2] = 0
				End If

				Transform(_Matrix[0], _Matrix[1], _Matrix[2], _Matrix[3], _Matrix[4], _Matrix[5])

				If (_flipNeeded) Then
					Transform(-1, 0, 0, 1, _halfWidth - origin.x, _halfHeight - origin.y)
				End If

				If (_renderer = Null) Then
					DrawImage(_pixels, -origin.x, -origin.y, _curIndex)
				Else
					_renderer.OnSpriteRender(-origin.x, -origin.y)
				End If
			PopMatrix()
		End If

	#If FLX_DEBUG_ENABLED = "1"
		_VisibleCount += 1;
		If (FlxG.VisualDebug And Not ignoreDrawDebug) DrawDebug(_camera);
	#End
	End Method

	Method DrawFrame:Void(force:Bool = False)
		If (force Or dirty) _CalcFrame()
	End Method

	Method AddAnimation:FlxAnim(name:String, frames:Int[], frameRate:Float = 0, looped:Bool = True)
		Local anim:FlxAnim = New FlxAnim(name, frames, frameRate, looped)
		_animations.Set(name, anim)

		Return anim
	End Method

	Method AddAnimation:FlxAnim(name:String, startFrame:Int, endFrame:Int, frameRate:Float = 0, looped:Bool = True)
		Local i:Int = 0
		Local l:Int = endFrame - startFrame + 1
		Local frames:Int[] = New Int[l]

		While (i < l)
			frames[i] = startFrame + i
			i += 1
		Wend

		Return AddAnimation(name, frames, frameRate, looped)
	End Method

	Method AddAnimation:FlxAnim(name:String, frameRate:Float = 0, looped:Bool = True)
		If (_pixels = Null) Return Null
		Return AddAnimation(name, 0, _pixels.Frames() -1, frameRate, looped)
	End Method

	Method AddAnimationCallback:Void(animationCallback:FlxAnimationListener)
		_callback = animationCallback
	End Method

	Method GetAnimation:FlxAnim(animName:String)
		Return _animations.Get(animName)
	End Method

	Method GetAnimation:FlxAnim()
		Return _curAnim
	End Method

	Method Play:Void(animName:String, force:Bool = False)
		If (Not force And _curAnim <> Null And animName = _curAnim.name And( _curAnim.looped Or Not finished )) Return

		_curFrame = 0
		_curIndex = 0
		_frameTimer = 0
		_paused = False

		Local anim:FlxAnim = _animations.Get(animName)

		If (anim <> Null) Then
			_curAnim = anim

			If (_curAnim.delay <= 0) Then
				finished = True
			Else
				finished = False
			End If

			_curIndex = _curAnim.frames[_curFrame]
			dirty = True
			Return
		End If

		FlxG.Log("WARNING: No animation called ~q" + animName + "~q")
	End Method

	Method Stop:Void()
		Frame = 0
	End Method

	Method Pause:Void()
		_paused = True
	End Method

	Method Resume:Void()
		_paused = False
	End Method

	Method RandomFrame:Void()
		_curAnim = Null

		If (_pixels <> Null) Then
			_curIndex = Int(FlxG.Random() * (_pixels.Frames() - 1))
		Else
			_curIndex = 0
		End If

		dirty = True
	End Method

	Method SetOriginToCorner:Void()
		origin.x = 0
		origin.y = 0
	End Method

	Method CenterOffsets:Void(adjustPosition:Bool = False)
		offset.x = (frameWidth - width) * .5
		offset.y = (frameHeight - height) * .5

		If (adjustPosition) Then
			x += offset.x
			y += offset.y
		End If
	End Method

	Method Pixels:Image() Property
		Return _pixels
	End Method

	Method Pixels:Void(pixels:Image) Property
		_pixels = pixels

		If (_pixels <> Null) Then
			width = pixels.Width()
			height = pixels.Height()
		Else
			width = 0
			height = 0
		End If

		frameWidth = width
		frameHeight = height
		_ResetHelpers()
	End Method

	Method Facing:Int() Property
		Return _facing
	End Method

	Method Facing:Void(direction:Int) Property
		If (_facing <> direction) dirty = True
		_facing = direction

		_flipNeeded = (_flipped And _facing = LEFT)
	End Method

	Method Alpha:Float() Property
		Return _alpha
	End Method

	Method Alpha:Void(alpha:Float) Property
		If (alpha < 0) alpha = 0
		_alpha = alpha
	End Method

	Method Color:Int() Property
		Return _color.argb
	End Method

	Method Color:Void(color:Int) Property
		_color.SetRGB(color)
	End Method

	Method Frame:Int() Property
		Return _curIndex
	End Method

	Method Frame:Void(frame:Int) Property
		_curAnim = Null
		_curIndex = frame
		_paused = False
		dirty = True
	End Method

	Method OnScreen:Bool(camera:FlxCamera = Null)
		If (camera = Null) camera = FlxG.Camera
		GetScreenXY(_point, camera)

		_point.x = _point.x - offset.x
		_point.y = _point.y - offset.y

		If ((angle = 0 Or _bakedRotation > 0) And scale.x = 1 And scale.y = 1) Then
			Return _point.x + frameWidth > 0 And _point.x < camera.Width And _point.y + frameHeight > 0 And _point.y < camera.Height
		End If

		Local radius:Float = Sqrt(_halfWidth * _halfWidth + _halfHeight * _halfHeight) * Max(Abs(scale.x), Abs(scale.y))
		_point.x += _halfWidth
		_point.y += _halfHeight

		Return _point.x + radius > 0 And _point.x - radius < camera.Width And _point.y + radius > 0 And _point.y - radius < camera.Height
	End Method

	Method SetRenderer:Void(render:FlxSpriteRenderer)
		_renderer = render
	End Method

	Method ClearRenderer:Void()
		_renderer = Null
	End Method

	Method _ResetHelpers:Void()
		_halfWidth = frameWidth * .5
		_halfHeight = frameHeight * .5
		origin.Make(_halfWidth, _halfHeight)

		If (_pixels <> Null) Then
			frames = _pixels.Frames()
		Else
			frames = 1
		End If

		_curIndex = 0
		_mixedColor.SetARGB(FlxG.WHITE)
	End Method

Private
	Method _UpdateAnimation:Void()
		If (_bakedRotation > 0) Then
			Local oldIndex:Int = _curIndex
			Local angleHelper:Int = angle Mod 360

			If (angleHelper < 0) angleHelper += 360
			_curIndex = angleHelper / _bakedRotation + 0.5

			If (oldIndex <> _curIndex) _CalcFrame()
			Return
		End If

		If (_paused) Return

		If (_curAnim <> Null And _curAnim.delay > 0 And (_curAnim.looped Or Not finished))
			_frameTimer += FlxG.Elapsed
			While (_frameTimer > _curAnim.delay)
				_frameTimer -= _curAnim.delay

				If (_curFrame = _curAnim.frames.Length() - 1) Then
					If (_curAnim.looped) _curFrame = 0
					finished = True
				Else
					_curFrame += 1
				End If

				_curIndex = _curAnim.frames[_curFrame]
				dirty = True
			Wend
		End If

		If (dirty) _CalcFrame()
	End Method

	Method _CalcFrame:Void()
		If (_callback <> Null) Then
			If (_curAnim <> Null) Then
				_callback.OnAnimationFrame(_curAnim.name, _curFrame, _curIndex)
			Else
				_callback.OnAnimationFrame("", _curFrame, _curIndex)
			End If
		End If
		dirty = False
	End Method

End Class

Interface FlxSpriteRenderer

	Method OnSpriteRender:Void(x:Float, y:Float)

End Interface

Private
Class FlxGraphicLoader Extends FlxResourceLoader<Image>

	Field name:String
	Field animated:Bool
	Field width:Float
	Field height:Float

	Method Load:Image(name:String)
		Local image:Image = LoadImage(FlxAssetsManager.GetImagePath(Self.name))

		If (Not animated) Then
			Return image
		Else
			Local frames:Int

			If (width = 0 And height = 0) Then
				width = image.Height()
			ElseIf (width = 0) Then
				width = height
			ElseIf (height = 0) Then
				height = width
			End If

			If (height = 0) Then
				frames = Ceil(image.Width() / width)
				height = width
			Else
				frames = Ceil((image.Width() * image.Height()) / (width * height))
			End If

			Return image.GrabImage(0, 0, width, height, frames)
		End If
	End Method

End Class

Strict

Import mojo

Import flxextern
Import flxpoint
Import flxobject
Import flxcamera
Import flxg

Import system.flxanim
Import system.flxcolor

Import "data/default_flx.png"

Class FlxSprite Extends FlxObject

	Global ClassObject:Object

	Field origin:FlxPoint

	Field offset:FlxPoint

	Field scale:FlxPoint

	Field blend:Int

	Field finished:Bool

	Field frameWidth:Int

	Field frameHeight:Int

	Field frames:Int

	Field dirty:Bool

	Field _color:FlxColor

	Field _mixedColor:FlxColor

	Field _camera:FlxCamera

Private
	Global _GraphicLoader:FlxGraphicLoader = New FlxGraphicLoader()

	Global _Matrix:Float[6]

	Field _animations:StringMap<FlxAnim>

	Field _flipped:Bool

	Field _flipNeeded:Bool

	Field _curAnim:FlxAnim

	Field _curFrame:Int

	Field _curIndex:Int

	Field _frameTimer:Float

	Field _paused:Bool

	Field _callback:FlxAnimationListener

	Field _facing:Int

	Field _alpha:Float

	Field _bakedRotation:Float

	Field _pixels:Image

	Field _halfWidth:Float

	Field _halfHeight:Float

	Field _renderer:FlxSpriteRenderer

Public
	Method New(x:Float = 0, y:Float = 0, simpleGraphic:String = "")
		Super.New(x, y)

		offset = New FlxPoint()
		origin = New FlxPoint()

		scale = New FlxPoint(1.0, 1.0)
		_alpha = 1
		_color = New FlxColor(FlxG.WHITE)
		blend = AlphaBlend
		_cameras = Null

		finished = False
		_facing = RIGHT
		_animations = New StringMap<FlxAnim>
		_flipped = False
		_curAnim = Null
		_curFrame = 0
		_curIndex = 0
		_frameTimer = 0
		_paused = False

		_callback = Null

		_mixedColor = New FlxColor(FlxG.WHITE)

		If (simpleGraphic.Length() = 0)
			simpleGraphic = "default" + FlxG.DATA_SUFFIX
		End If

		LoadGraphic(simpleGraphic)
	End Method

	Method Destroy:Void()
		If (_animations <> Null) Then
			_animations.Clear()
			_animations = Null
		End If

		offset = Null
		origin = Null
		scale = Null
		_curAnim = Null
		_callback = Null
		_color = Null
		_mixedColor = Null
		_camera = Null
		_renderer = Null

		Super.Destroy()
	End Method

	Method LoadGraphic:FlxSprite(graphic:String, animated:Bool = False, reverse:Bool = False, width:Int = 0, height:Int = 0, unique:Bool = False)
		_bakedRotation = 0

		_GraphicLoader.name = graphic
		_GraphicLoader.animated = animated
		_GraphicLoader.width = width
		_GraphicLoader.height = height

		_pixels = FlxG.AddBitmap(graphic, _GraphicLoader, unique)

		_flipped = reverse

		Self.width = _pixels.Width()
		frameWidth = Self.width

		Self.height = _pixels.Height()
		frameHeight = Self.height

		_ResetHelpers()

		Return Self
	End Method

	Method LoadRotatedGraphic:FlxSprite(graphic:String, rotations:Int = 16, frame:Int = -1)
		Error "LoadRotatedGraphic not currenlty support in " + FlxG.LIBRARY_NAME
		'TODO
		Return Null
	End Method

	Method MakeGraphic:FlxSprite(width:Int, height:Int, color:Int = FlxG.WHITE, unique:Bool = False, key:String = "")
		_pixels = FlxG.CreateBitmap(width, height, color, unique, key)
		_bakedRotation = 0

		Self.width = width
		frameWidth = width
		Self.height = height
		frameHeight = height

		_ResetHelpers()

		Return Self
	End Method

	Method PostUpdate:Void()
		Super.PostUpdate()
		_UpdateAnimation()
	End Method

	Method Draw:Void()
		If (_flickerTimer <> 0) Then
			_flicker = Not _flicker
			If (_flicker) Return
		End If

		_camera = FlxG._CurrentCamera

		If (_cameras <> Null And Not _cameras.Contains(_camera.ID)) Return
		If (Not OnScreen(_camera)) Return

		If (dirty) _CalcFrame()

		If (FlxG._LastDrawingBlend <> blend) Then
			SetBlend(blend)
			FlxG._LastDrawingBlend = blend
		End If

		_point.x = x - int(_camera.scroll.x * scrollFactor.x) - offset.x
		_point.y = y - int(_camera.scroll.y * scrollFactor.y) - offset.y

		If (_point.x > 0) Then
			_point.x += 0.0000001
		Else
			_point.x -= 0.0000001
		End If

		If (_point.y > 0) Then
			_point.y += 0.0000001
		Else
			_point.y -= 0.0000001
		End If

		If (_camera.Color <> FlxG.WHITE) Then
			_mixedColor.MixRGB(_color, _camera._color)

			If (FlxG._LastDrawingColor <> _mixedColor.argb) Then
				SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
				FlxG._LastDrawingColor = _mixedColor.argb
			End If
		Else
			If (FlxG._LastDrawingColor <> _color.argb) Then
				SetColor(_color.r, _color.g, _color.b)
				FlxG._LastDrawingColor = _color.argb
			End If
		End If

		If (_camera.Alpha < 1) Then
			Local _mixedAlpha:Float = _camera.Alpha * _alpha

			If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
				SetAlpha(_mixedAlpha)
				FlxG._LastDrawingAlpha = _mixedAlpha
			End If
		Else
			If (FlxG._LastDrawingAlpha <> _alpha) Then
				SetAlpha(_alpha)
				FlxG._LastDrawingAlpha = _alpha
			End If
		End If

		If ((angle = 0 Or _bakedRotation > 0) And scale.x = 1 And scale.y = 1) Then
			If ( Not _flipNeeded) Then
				If (_renderer = Null) Then
					DrawImage(_pixels, _point.x, _point.y, _curIndex)
				Else
					_renderer.OnSpriteRender(_point.x, _point.y)
				End If
			Else
				PushMatrix()
					Transform(-1, 0, 0, 1, _point.x + _halfWidth, _point.y + _halfHeight)

					If (_renderer = Null) Then
						DrawImage(_pixels, -_halfWidth, -_halfHeight, _curIndex)
					Else
						_renderer.OnSpriteRender(-_halfWidth, -_halfHeight)
					End If
				PopMatrix()
			End If
		Else
			PushMatrix()
				'Translate
				_Matrix[4] = _point.x + origin.x
				_Matrix[5] = _point.y + origin.y

				'Scale
				_Matrix[0] = scale.x
				_Matrix[3] = scale.y

				'Rotate
				If (angle <> 0 And _bakedRotation = 0) Then
					Local sin:Float = Sin(angle)
					Local cos:Float = Cos(angle)

					_Matrix[1] = sin * _Matrix[0]
					_Matrix[2] = -sin * _Matrix[3]
					_Matrix[0] *= cos
					_Matrix[3] *= cos
				Else
					_Matrix[1] = 0
					_Matrix[2] = 0
				End If

				Transform(_Matrix[0], _Matrix[1], _Matrix[2], _Matrix[3], _Matrix[4], _Matrix[5])

				If (_flipNeeded) Then
					Transform(-1, 0, 0, 1, _halfWidth - origin.x, _halfHeight - origin.y)
				End If

				If (_renderer = Null) Then
					DrawImage(_pixels, -origin.x, -origin.y, _curIndex)
				Else
					_renderer.OnSpriteRender(-origin.x, -origin.y)
				End If
			PopMatrix()
		End If

	#If FLX_DEBUG_ENABLED = "1"
		_VisibleCount += 1;
		If (FlxG.VisualDebug And Not ignoreDrawDebug) DrawDebug(_camera);
	#End
	End Method

	Method DrawFrame:Void(force:Bool = False)
		If (force Or dirty) _CalcFrame()
	End Method

	Method AddAnimation:FlxAnim(name:String, frames:Int[], frameRate:Float = 0, looped:Bool = True)
		Local anim:FlxAnim = New FlxAnim(name, frames, frameRate, looped)
		_animations.Set(name, anim)

		Return anim
	End Method

	Method AddAnimation:FlxAnim(name:String, startFrame:Int, endFrame:Int, frameRate:Float = 0, looped:Bool = True)
		Local i:Int = 0
		Local l:Int = endFrame - startFrame + 1
		Local frames:Int[] = New Int[l]

		While (i < l)
			frames[i] = startFrame + i
			i += 1
		Wend

		Return AddAnimation(name, frames, frameRate, looped)
	End Method

	Method AddAnimation:FlxAnim(name:String, frameRate:Float = 0, looped:Bool = True)
		If (_pixels = Null) Return Null
		Return AddAnimation(name, 0, _pixels.Frames(), frameRate, looped)
	End Method

	Method AddAnimationCallback:Void(animationCallback:FlxAnimationListener)
		_callback = animationCallback
	End Method

	Method GetAnimation:FlxAnim(animName:String)
		Return _animations.Get(animName)
	End Method

	Method GetAnimation:FlxAnim()
		Return _curAnim
	End Method

	Method Play:Void(animName:String, force:Bool = False)
		If (Not force And _curAnim <> Null And animName = _curAnim.name And( _curAnim.looped Or Not finished )) Return

		_curFrame = 0
		_curIndex = 0
		_frameTimer = 0
		_paused = False

		Local anim:FlxAnim = _animations.Get(animName)

		If (anim <> Null) Then
			_curAnim = anim

			If (_curAnim.delay <= 0) Then
				finished = True
			Else
				finished = False
			End If

			_curIndex = _curAnim.frames[_curFrame]
			dirty = True
			Return
		End If

		FlxG.Log("WARNING: No animation called ~q" + animName + "~q")
	End Method

	Method Stop:Void()
		Frame = 0
	End Method

	Method Pause:Void()
		_paused = True
	End Method

	Method Resume:Void()
		_paused = False
	End Method

	Method RandomFrame:Void()
		_curAnim = Null

		If (_pixels <> Null) Then
			_curIndex = Int(FlxG.Random() * (_pixels.Frames() - 1))
		Else
			_curIndex = 0
		End If

		dirty = True
	End Method

	Method SetOriginToCorner:Void()
		origin.x = 0
		origin.y = 0
	End Method

	Method CenterOffsets:Void(adjustPosition:Bool = False)
		offset.x = (frameWidth - width) * .5
		offset.y = (frameHeight - height) * .5

		If (adjustPosition) Then
			x += offset.x
			y += offset.y
		End If
	End Method

	Method Pixels:Image() Property
		Return _pixels
	End Method

	Method Pixels:Void(pixels:Image) Property
		_pixels = pixels

		If (_pixels <> Null) Then
			width = pixels.Width()
			height = pixels.Height()
		Else
			width = 0
			height = 0
		End If

		frameWidth = width
		frameHeight = height
		_ResetHelpers()
	End Method

	Method Facing:Int() Property
		Return _facing
	End Method

	Method Facing:Void(direction:Int) Property
		If (_facing <> direction) dirty = True
		_facing = direction

		_flipNeeded = (_flipped And _facing = LEFT)
	End Method

	Method Alpha:Float() Property
		Return _alpha
	End Method

	Method Alpha:Void(alpha:Float) Property
		If (alpha < 0) alpha = 0
		_alpha = alpha
	End Method

	Method Color:Int() Property
		Return _color.argb
	End Method

	Method Color:Void(color:Int) Property
		_color.SetRGB(color)
	End Method

	Method Frame:Int() Property
		Return _curIndex
	End Method

	Method Frame:Void(frame:Int) Property
		_curAnim = Null
		_curIndex = frame
		_paused = False
		dirty = True
	End Method

	Method OnScreen:Bool(camera:FlxCamera = Null)
		If (camera = Null) camera = FlxG.Camera
		GetScreenXY(_point, camera)

		_point.x = _point.x - offset.x
		_point.y = _point.y - offset.y

		If ((angle = 0 Or _bakedRotation > 0) And scale.x = 1 And scale.y = 1) Then
			Return _point.x + frameWidth > 0 And _point.x < camera.Width And _point.y + frameHeight > 0 And _point.y < camera.Height
		End If

		Local radius:Float = Sqrt(_halfWidth * _halfWidth + _halfHeight * _halfHeight) * Max(Abs(scale.x), Abs(scale.y))
		_point.x += _halfWidth
		_point.y += _halfHeight

		Return _point.x + radius > 0 And _point.x - radius < camera.Width And _point.y + radius > 0 And _point.y - radius < camera.Height
	End Method

	Method SetRenderer:Void(render:FlxSpriteRenderer)
		_renderer = render
	End Method

	Method ClearRenderer:Void()
		_renderer = Null
	End Method

	Method _ResetHelpers:Void()
		_halfWidth = frameWidth * .5
		_halfHeight = frameHeight * .5
		origin.Make(_halfWidth, _halfHeight)

		If (_pixels <> Null) Then
			frames = _pixels.Frames()
		Else
			frames = 1
		End If

		_curIndex = 0
		_mixedColor.SetARGB(FlxG.WHITE)
	End Method

Private
	Method _UpdateAnimation:Void()
		If (_bakedRotation > 0) Then
			Local oldIndex:Int = _curIndex
			Local angleHelper:Int = angle Mod 360

			If (angleHelper < 0) angleHelper += 360
			_curIndex = angleHelper / _bakedRotation + 0.5

			If (oldIndex <> _curIndex) _CalcFrame()
			Return
		End If

		If (_paused) Return

		If (_curAnim <> Null And _curAnim.delay > 0 And (_curAnim.looped Or Not finished))
			_frameTimer += FlxG.Elapsed
			While (_frameTimer > _curAnim.delay)
				_frameTimer -= _curAnim.delay

				If (_curFrame = _curAnim.frames.Length() - 1) Then
					If (_curAnim.looped) _curFrame = 0
					finished = True
				Else
					_curFrame += 1
				End If

				_curIndex = _curAnim.frames[_curFrame]
				dirty = True
			Wend
		End If

		If (dirty) _CalcFrame()
	End Method

	Method _CalcFrame:Void()
		If (_callback <> Null) Then
			If (_curAnim <> Null) Then
				_callback.OnAnimationFrame(_curAnim.name, _curFrame, _curIndex)
			Else
				_callback.OnAnimationFrame("", _curFrame, _curIndex)
			End If
		End If
		dirty = False
	End Method

End Class

Interface FlxSpriteRenderer

	Method OnSpriteRender:Void(x:Float, y:Float)

End Interface

Private
Class FlxGraphicLoader Extends FlxResourceLoader<Image>

	Field name:String
	Field animated:Bool
	Field width:Float
	Field height:Float

	Method Load:Image(name:String)
		Local image:Image = LoadImage(FlxAssetsManager.GetImagePath(Self.name))

		If (Not animated) Then
			Return image
		Else
			Local frames:Int

			If (width = 0 And height = 0) Then
				width = image.Height()
			ElseIf (width = 0) Then
				width = height
			ElseIf (height = 0) Then
				height = width
			End If

			If (height = 0) Then
				frames = Ceil(image.Width() / width)
				height = width
			Else
				frames = Ceil((image.Width() * image.Height()) / (width * height))
			End If

			Return image.GrabImage(0, 0, width, height, frames)
		End If
	End Method

End Class


New SceneManager method Goto(Scene) added

Strict

Private

Import director
Import scene

Public

Class SceneManager
    Private

    Field scenes:StringMap<Scene> = New StringMap<Scene>()
    Field created:List<String> = New List<String>()
    Field _current:Scene
    Field _prevScene:Scene
    Field _nextScene:Scene
    Field _director:Director

    Public

    Method New(director:Director)
        _director = director
    End

    Method Add:Void(scene:Scene)
        If scenes.Contains(scene.name)
            Error("Scenemanager already contains a scene named " + scene.name)
        End

        scene.scenes = Self
        scenes.Set(scene.name, scene)
    End

    Method Get:Scene(name:String)
        Return scenes.Get(name)
    End

    Method Goto:Void(scene:Scene)
        Goto(scene.name)
    End

    Method Goto:Void(name:String)
        If _nextScene And _nextScene.name = name Then Return
        _prevScene = _current
        _nextScene = scenes.Get(name)

        If Not created.Contains(name)
            created.AddLast(name)
            _nextScene.OnCreate()
        End

        If _current Then _current.OnLeave()
        _nextScene.OnEnter()

        _current = _nextScene
    End

    Method nextScene:Scene() Property
        Return _nextScene
    End

    Method prevScene:Scene() Property
        Return _prevScene
    End

    Method scene:Scene() Property
        Return _current
    End

    Method director:Director() Property
        Return _director
    End
End

Strict

Private

Import director
Import scene

Public

Class SceneManager
    Private

    Field scenes:StringMap<Scene> = New StringMap<Scene>()
    Field created:List<String> = New List<String>()
    Field _current:Scene
    Field _prevScene:Scene
    Field _nextScene:Scene
    Field _director:Director

    Public

    Method New(director:Director)
        _director = director
    End

    Method Add:Void(scene:Scene)
        If scenes.Contains(scene.name)
            Error("Scenemanager already contains a scene named " + scene.name)
        End

        scene.scenes = Self
        scenes.Set(scene.name, scene)
    End

    Method Get:Scene(name:String)
        Return scenes.Get(name)
    End

    Method Goto:Void(name:String)
        If _nextScene And _nextScene.name = name Then Return
        _prevScene = _current
        _nextScene = scenes.Get(name)

        If Not created.Contains(name)
            created.AddLast(name)
            _nextScene.OnCreate()
        End

        If _current Then _current.OnLeave()
        _nextScene.OnEnter()

        _current = _nextScene
    End

    Method nextScene:Scene() Property
        Return _nextScene
    End

    Method prevScene:Scene() Property
        Return _prevScene
    End

    Method scene:Scene() Property
        Return _current
    End

    Method director:Director() Property
        Return _director
    End
End


Added BackEase functions

Added BackEase functions

#Rem
Copyright (c) 2011 Steve Revill and Shane Woolcock
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.
#End

Function QuadTween:Float(b:Float, c:Float, t:Float, d:Float = 1)
	Local diff:Float = c - b
	t /= d / 2
	If t < 1 Return diff / 2 * t * t + b
	t -= 1
	Return -diff / 2 * (t * (t - 2) - 1) + b
End

Function QuinticTween:Float(b:Float, c:Float, t:Float, d:Float = 1)
	Local diff:Float = c - b
	t /= d / 2
	If (t < 1) Return diff / 2 * t * t * t * t * t + b
	t -= 2
	Return diff / 2 * (t * t * t * t * t + 2) + b
End

Function TweenCalc:Float(p1:Float, p2:Float, t:Float)
	Return p1 + t * (p2 - p1)
End

Function TweenSmooth:Float(p1:Float, p2:Float, t:Float)
	Local v:Float = SmoothStep(t)
	Return p1 + v * (p2 - p1)
End

Function SmoothStep:Float(x:Float, interpSmooth:Int = 1)
	For Local i:Int = 0 Until interpSmooth
		x *= x * (3 - 2 * x)
	Next

	Return x' x*x * (3-2*x)
End

Function TweenUp:Float(p1:Float, p2:Float, t:Float)
	Local v:Float = SmoothStep(t)
	v = Pow(v, 2) 'power of 2.
	Return p1 + v * (p2 - p1)
End

Function TweenDown:Float(p1:Float, p2:Float, t:Float)
	Local v:Float = SmoothStep(t)
	v = 1 - Pow(1 - v, 2) 'InvSquared
	Return p1 + v * (p2 - p1)
End

Function Bezier:Float(p1:Float, p2:Float, cp1:Float, cp2:Float, t:Float)
	Local v:Float = p1 * Pow( (1 - t), 3) + 3 * cp1 * Pow( (1 - t), 2) * t + 3 * cp2 * (1 - t) * Pow(t, 2) + p2 * Pow(t, 3)
	Return v
End

Function EaseInBounceTween:Float(b:Float, c:Float, t:Float, d:Float = 1)
	Local diff:Float = c - b
	Return diff - EaseOutBounceTween(d - t, 0, diff, d) + b
End

Function EaseOutBounceTween:Float(b:Float, c:Float, t:Float, d:Float = 1)
	Local diff:Float = c - b
	t /= d
	If (t < (1 / 2.75))
		Return diff * (7.5625 * t * t) + b
	ElseIf(t < (2 / 2.75))
		t -= (1.5 / 2.75)
		Return diff * (7.5625 * t * t + 0.75) + b
	ElseIf(t < (2.5 / 2.75))
		t -= (2.25 / 2.75)
		Return diff * (7.5625 * t * t + 0.9375) + b
	Else
		t -= (2.625 / 2.75)
		Return diff * (7.5625 * t * t + 0.984375) + b
	End
End

Function EaseInOutBounceTween:Float(b:Float, c:Float, t:Float, d:Float = 1)
	Local diff:Float = c - b
	If (t < d / 2)
		Return EaseInBounceTween(t * 2, 0, diff, d) * 0.5 + b
	End
	Return EaseOutBounceTween(t * 2 - d, 0, diff, d) * 0.5 + diff * 0.5 + b
End

Function BackEaseInTween:Float(b:Float, c:Float, t:Float, d:Float = 1, s:Float = 2)
	Local diff:Float = c - b
	t /= d
	Return diff * t * t * ( (s + 1) * t - s) + b
End

Function BackEaseOutTween:Float(b:Float, c:Float, t:Float, d:Float = 1, s:Float = 2)
	Local diff:Float = c - b
	t = t / d - 1
	Return diff * (t * t * ( (s + 1) * t + s) + 1) + b
End

Function BackEaseInOutTween:Float(b:Float, c:Float, t:Float, d:Float = 1, s:Float = 2)
	Local diff:Float = c - b
	Local s2:Float = s
	t /= d / 2
	s2 *= 1.525
	If t < 1
		Return diff / 2 * (t * t * ( (s2 + 1) * t - s2)) + b
	End
	t -= 2
	Return diff / 2 * (t * t * ( (s2 + 1) * t + s2) + 2) + b
End


ignore OnUpdate events with timer.delta = 0

Strict

Private

Import bono
Import brl.asyncevent
Import mojo.app
Import mojo.graphics

Public

Class App Extends app.App Abstract
    Private

    Field timer:DeltaTimer
    Field contentScaler:ContentScaler

    Public

    Const DEFAULT_FPS:Int = 60
    Field renderable:Renderable
    Field suspendable:Suspendable
    Field updateable:Updateable

    Method Run:Void() Abstract

    Method TranslateSpace:Vector2D(vec:Vector2D)
        If Not contentScaler Then Return vec.Copy()
        Return contentScaler.TranslateSpace(Self, vec)
    End

    Method GetDirector:Director()
        Return Director.Shared()
    End

    Method SetHandler:Void(obj:Object)
        renderable = Renderable(obj)
        suspendable = Suspendable(obj)
        updateable = Updateable(obj)
    End

    ' --- CONFIG

    Method GetTargetFps:Int()
        Return DEFAULT_FPS
    End

    Method GetContentScaler:ContentScaler()
        Return Null
    End

    Method GetVirtualSize:Vector2D()
        Return Device.GetSize()
    End

    ' --- MONKEY APP

    Method OnCreate:Int()
        Try
            SetUpdateRate(GetTargetFps())
            contentScaler = GetContentScaler()

            Director.Shared().SetApp(Self)
            Run()
        Catch ex:Exception
            Error ex
        End
        Return 0
    End

    Method OnRender:Int()
        Try
            Cls(0, 0, 0)
            UpdateAsyncEvents()
            If contentScaler Then contentScaler.OnRenderPre(Self)
            If renderable Then renderable.OnRender()
            If contentScaler Then contentScaler.OnRenderPost(Self)
        Catch ex:Exception
            Error ex
        End
        Return 0
    End

    Method OnUpdate:Int()
        If Not timer Then timer = New DeltaTimer(GetTargetFps())

        Try
            timer.OnUpdate()
            If timer.delta <> 0 And updateable Then updateable.OnUpdate(timer)
        Catch ex:Exception
            Error ex
        End
        Return 0
    End

    Method OnResume:Int()
        Try
            timer.Play()
            If suspendable Then suspendable.OnResume()
        Catch ex:Exception
            Error ex
        End
        Return 0
    End

    Method OnSuspend:Int()
        Try
            timer.Pause()
            If suspendable Then suspendable.OnSuspend()
        Catch ex:Exception
            Error ex
        End
        Return 0
    End
End

Strict

Private

Import bono
Import brl.asyncevent
Import mojo.app
Import mojo.graphics

Public

Class App Extends app.App Abstract
    Private

    Field timer:DeltaTimer
    Field contentScaler:ContentScaler

    Public

    Const DEFAULT_FPS:Int = 60
    Field renderable:Renderable
    Field suspendable:Suspendable
    Field updateable:Updateable

    Method Run:Void() Abstract

    Method TranslateSpace:Vector2D(vec:Vector2D)
        If Not contentScaler Then Return vec.Copy()
        Return contentScaler.TranslateSpace(Self, vec)
    End

    Method GetDirector:Director()
        Return Director.Shared()
    End

    Method SetHandler:Void(obj:Object)
        renderable = Renderable(obj)
        suspendable = Suspendable(obj)
        updateable = Updateable(obj)
    End

    ' --- CONFIG

    Method GetTargetFps:Int()
        Return DEFAULT_FPS
    End

    Method GetContentScaler:ContentScaler()
        Return Null
    End

    Method GetVirtualSize:Vector2D()
        Return Device.GetSize()
    End

    ' --- MONKEY APP

    Method OnCreate:Int()
        Try
            SetUpdateRate(GetTargetFps())
            contentScaler = GetContentScaler()

            Director.Shared().SetApp(Self)
            Run()
        Catch ex:Exception
            Error ex
        End
        Return 0
    End

    Method OnRender:Int()
        Try
            Cls(0, 0, 0)
            UpdateAsyncEvents()
            If contentScaler Then contentScaler.OnRenderPre(Self)
            If renderable Then renderable.OnRender()
            If contentScaler Then contentScaler.OnRenderPost(Self)
        Catch ex:Exception
            Error ex
        End
        Return 0
    End

    Method OnUpdate:Int()
        If Not timer Then timer = New DeltaTimer(GetTargetFps())

        Try
            timer.OnUpdate()
            If updateable Then updateable.OnUpdate(timer)
        Catch ex:Exception
            Error ex
        End
        Return 0
    End

    Method OnResume:Int()
        Try
            timer.Play()
            If suspendable Then suspendable.OnResume()
        Catch ex:Exception
            Error ex
        End
        Return 0
    End

    Method OnSuspend:Int()
        Try
            timer.Pause()
            If suspendable Then suspendable.OnSuspend()
        Catch ex:Exception
            Error ex
        End
        Return 0
    End
End


makeGraphic flag for FlxTileBlock

makeGraphic flag for FlxTileBlock

Strict

Import mojo

Import flxsprite
Import flxg

Class FlxTileblock Extends FlxSprite Implements FlxSpriteRenderer

	Global ClassObject:Object

Private
	Field _tilePixels:Image

	Field _stamps:Stack<FlxStamp>

Public
	Method New(x:Int, y:Int, width:Int, height:Int, makeGraphic:Bool = False)
		Super.New(x, y)

		SetRenderer(Self)
		If (makeGraphic) MakeGraphic(width, height, 0)
		active = False
		immovable = True
		moves = False

		_stamps = New Stack<FlxStamp>()
	End Method

	Method Destroy:Void()
		If (_tilePixels <> Null) Then
			_tilePixels = Null
		End If

		If (_stamps.Length() > 0) Then
			Local i:Int = 0
			Local l:Int = _stamps.Length()
			Local stamp:FlxStamp

			While (i < l)
				_stamps.Set(i, Null)
				i += 1
			Wend

			_stamps.Clear()
		End If

		Super.Destroy()
	End Method

	Method LoadTiles:FlxTileblock(tileGraphic:String, tileWidth:Int = 0, tileHeight:Int = 0, empties:Int = 0)
		If (tileGraphic.Length() = 0) Return Self

		Local sprite:FlxSprite = (New FlxSprite()).LoadGraphic(tileGraphic, True, False, tileWidth, tileHeight)
		Local spriteWidth:Int = sprite.width
		Local spriteHeight:Int = sprite.height
		Local total:Int = sprite.frames + empties

		_tilePixels = sprite.Pixels

		If (width Mod spriteWidth <> 0) Then
			width = Int(width / spriteWidth + 1) * spriteWidth
		End If

		If (height Mod spriteHeight <> 0) Then
			height = Int(height / spriteHeight + 1) * spriteHeight
		End If

		If (_tilePixels <> Null) MakeGraphic(width, height, FlxG.WHITE)

		Local row:Int = 0
		Local column:Int
		Local destinationX:Int
		Local destinationY:Int = 0
		Local widthInTiles:Int = width / spriteWidth
		Local heightInTiles:Int = height / spriteHeight
		Local i:Int = 0

		_stamps.Clear()

		While (row < heightInTiles)
			destinationX = 0
			column = 0

			While (column < widthInTiles)
				If (FlxG.Random() * total > empties) Then
					_stamps.Push(New FlxStamp(destinationX, destinationY, Int(FlxG.Random() * (sprite.frames - 1))))
				End If

				destinationX += spriteWidth
				column += 1
			Wend

			destinationY += spriteHeight
			row += 1
		Wend

		Return Self
	End Method

	Method OnSpriteRender:Void(x:Float, y:Float)
		If (_tilePixels = Null) Return

		Local i:Int = 0
		Local l:Int = _stamps.Length()
		Local stamp:FlxStamp

		While (i < l)
			stamp = _stamps.Get(i)
			DrawImage(_tilePixels, x + stamp.x, y + stamp.y, stamp.frame)
			i += 1
		Wend
	End Method

End Class

Private
Class FlxStamp

	Field x:Int

	Field y:Int

	Field frame:Int

	Method New(x:Int, y:Int, frame:Int)
		Self.x = x
		Self.y = y
		Self.frame = frame
	End Method

End Class

Strict

Import mojo

Import flxsprite
Import flxg

Class FlxTileblock Extends FlxSprite Implements FlxSpriteRenderer

	Global ClassObject:Object

Private
	Field _tilePixels:Image

	Field _stamps:Stack<FlxStamp>

Public
	Method New(x:Int, y:Int, width:Int, height:Int)
		Super.New(x, y)

		SetRenderer(Self)
		MakeGraphic(width, height, 0)
		active = False
		immovable = True
		moves = False

		_stamps = New Stack<FlxStamp>()
	End Method

	Method Destroy:Void()
		If (_tilePixels <> Null) Then
			_tilePixels = Null
		End If

		If (_stamps.Length() > 0) Then
			Local i:Int = 0
			Local l:Int = _stamps.Length()
			Local stamp:FlxStamp

			While (i < l)
				_stamps.Set(i, Null)
				i += 1
			Wend

			_stamps.Clear()
		End If

		Super.Destroy()
	End Method

	Method LoadTiles:FlxTileblock(tileGraphic:String, tileWidth:Int = 0, tileHeight:Int = 0, empties:Int = 0)
		If (tileGraphic.Length() = 0) Return Self

		Local sprite:FlxSprite = (New FlxSprite()).LoadGraphic(tileGraphic, True, False, tileWidth, tileHeight)
		Local spriteWidth:Int = sprite.width
		Local spriteHeight:Int = sprite.height
		Local total:Int = sprite.frames + empties

		_tilePixels = sprite.Pixels

		If (width Mod spriteWidth <> 0) Then
			width = Int(width / spriteWidth + 1) * spriteWidth
		End If

		If (height Mod spriteHeight <> 0) Then
			height = Int(height / spriteHeight + 1) * spriteHeight
		End If

		If (_tilePixels <> Null) MakeGraphic(width, height, FlxG.WHITE)

		Local row:Int = 0
		Local column:Int
		Local destinationX:Int
		Local destinationY:Int = 0
		Local widthInTiles:Int = width / spriteWidth
		Local heightInTiles:Int = height / spriteHeight
		Local i:Int = 0

		_stamps.Clear()

		While (row < heightInTiles)
			destinationX = 0
			column = 0

			While (column < widthInTiles)
				If (FlxG.Random() * total > empties) Then
					_stamps.Push(New FlxStamp(destinationX, destinationY, Int(FlxG.Random() * (sprite.frames - 1))))
				End If

				destinationX += spriteWidth
				column += 1
			Wend

			destinationY += spriteHeight
			row += 1
		Wend

		Return Self
	End Method

	Method OnSpriteRender:Void(x:Float, y:Float)
		If (_tilePixels = Null) Return

		Local i:Int = 0
		Local l:Int = _stamps.Length()
		Local stamp:FlxStamp

		While (i < l)
			stamp = _stamps.Get(i)
			DrawImage(_tilePixels, x + stamp.x, y + stamp.y, stamp.frame)
			i += 1
		Wend
	End Method

End Class

Private
Class FlxStamp

	Field x:Int

	Field y:Int

	Field frame:Int

	Method New(x:Int, y:Int, frame:Int)
		Self.x = x
		Self.y = y
		Self.frame = frame
	End Method

End Class


declare class with abstract method as abstract too

Strict

Private

Import bono
Import mojo.graphics

Public

Class BaseShape Extends BaseDisplayObject Implements Renderable, Rotateable, Updateable Abstract
    Private

    Field rotation:Float
    Field timelineFactory:TimelineFactory
    Field renderPos := New Vector2D()
    Field halfSize := New Vector2D()

    Public

    Method GetRotation:Float()
        Return rotation
    End

    Method SetRotation:Void(rotation:Float)
        Self.rotation = MathHelper.ModF(rotation, 360.0)
    End

    Method GetTimeline:TimelineFactory()
        If Not timelineFactory Then timelineFactory = New TimelineFactory(Self)
        Return timelineFactory
    End

    Method OnUpdate:Void(timer:DeltaTimer)
        If timelineFactory Then timelineFactory.GetTimeline().OnUpdate(timer)
    End

    Method OnRender:Void()
        halfSize.Set(GetSize()).Div(2)
        renderPos.Set(GetPosition())
        Align.Align(renderPos, Self, GetAlignment())

        PushMatrix()

        MatrixHelper.Translate(renderPos)

        MatrixHelper.Translate(halfSize)
        Rotate(GetRotation())
        MatrixHelper.Translate(halfSize.Revert())

        GetColor().Activate()
        OnRenderShape()
        GetColor().Deactivate()

        PopMatrix()
    End

    Method OnRenderShape:Void() Abstract
End

Strict

Private

Import bono
Import mojo.graphics

Public

Class BaseShape Extends BaseDisplayObject Implements Renderable, Rotateable, Updateable
    Private

    Field rotation:Float
    Field timelineFactory:TimelineFactory
    Field renderPos := New Vector2D()
    Field halfSize := New Vector2D()

    Public

    Method GetRotation:Float()
        Return rotation
    End

    Method SetRotation:Void(rotation:Float)
        Self.rotation = MathHelper.ModF(rotation, 360.0)
    End

    Method GetTimeline:TimelineFactory()
        If Not timelineFactory Then timelineFactory = New TimelineFactory(Self)
        Return timelineFactory
    End

    Method OnUpdate:Void(timer:DeltaTimer)
        If timelineFactory Then timelineFactory.GetTimeline().OnUpdate(timer)
    End

    Method OnRender:Void()
        halfSize.Set(GetSize()).Div(2)
        renderPos.Set(GetPosition())
        Align.Align(renderPos, Self, GetAlignment())

        PushMatrix()

        MatrixHelper.Translate(renderPos)

        MatrixHelper.Translate(halfSize)
        Rotate(GetRotation())
        MatrixHelper.Translate(halfSize.Revert())

        GetColor().Activate()
        OnRenderShape()
        GetColor().Deactivate()

        PopMatrix()
    End

    Method OnRenderShape:Void() Abstract
End


add tests for MathHelper.ModF

add tests for MathHelper.ModF

Strict

Private

Import bono

Public

Class MathHelperTest Extends TestCase
    Method TestModF:Void()
        AssertEquals(0.5, MathHelper.ModF(1.5, 1.0))
        AssertEquals(0.0, MathHelper.ModF(1.0, 1.0))
    End

    Method TestRoundUp:Void()
       AssertEquals(1, MathHelper.Round(0.5))
       AssertEquals(1, MathHelper.Round(0.6))
       AssertEquals(1, MathHelper.Round(0.7))
       AssertEquals(1, MathHelper.Round(0.8))
       AssertEquals(1, MathHelper.Round(0.9))
    End

    Method TestRoundUpWithNumberGreaterThanOne:Void()
       AssertEquals(10, MathHelper.Round(9.5))
       AssertEquals(10, MathHelper.Round(9.6))
       AssertEquals(10, MathHelper.Round(9.7))
       AssertEquals(10, MathHelper.Round(9.8))
       AssertEquals(10, MathHelper.Round(9.9))
    End

    Method TestRoundDown:Void()
       AssertEquals(0, MathHelper.Round(0.4))
       AssertEquals(0, MathHelper.Round(0.3))
       AssertEquals(0, MathHelper.Round(0.2))
       AssertEquals(0, MathHelper.Round(0.1))
    End

    Method TestRoundDownWithNumberGreaterThanOne:Void()
       AssertEquals(9, MathHelper.Round(9.4))
       AssertEquals(9, MathHelper.Round(9.3))
       AssertEquals(9, MathHelper.Round(9.2))
       AssertEquals(9, MathHelper.Round(9.1))
    End

    Method TestRoundAlreadyRounded:Void()
        AssertEquals(1, MathHelper.Round(1))
    End

    Method TestHextToInt:Void()
        AssertEquals(0, MathHelper.HexToInt("0"))
        AssertEquals(1, MathHelper.HexToInt("1"))
        AssertEquals(2, MathHelper.HexToInt("2"))
        AssertEquals(3, MathHelper.HexToInt("3"))
        AssertEquals(4, MathHelper.HexToInt("4"))
        AssertEquals(5, MathHelper.HexToInt("5"))
        AssertEquals(6, MathHelper.HexToInt("6"))
        AssertEquals(7, MathHelper.HexToInt("7"))
        AssertEquals(8, MathHelper.HexToInt("8"))
        AssertEquals(9, MathHelper.HexToInt("9"))
        AssertEquals(10, MathHelper.HexToInt("A"))
        AssertEquals(11, MathHelper.HexToInt("B"))
        AssertEquals(12, MathHelper.HexToInt("C"))
        AssertEquals(13, MathHelper.HexToInt("D"))
        AssertEquals(14, MathHelper.HexToInt("E"))
        AssertEquals(15, MathHelper.HexToInt("F"))
        AssertEquals(48879, MathHelper.HexToInt("BEEF"))
    End

    Method TestIntToHex:Void()
        AssertEquals("0", MathHelper.IntToHex(0))
        AssertEquals("1", MathHelper.IntToHex(1))
        AssertEquals("2", MathHelper.IntToHex(2))
        AssertEquals("3", MathHelper.IntToHex(3))
        AssertEquals("4", MathHelper.IntToHex(4))
        AssertEquals("5", MathHelper.IntToHex(5))
        AssertEquals("6", MathHelper.IntToHex(6))
        AssertEquals("7", MathHelper.IntToHex(7))
        AssertEquals("8", MathHelper.IntToHex(8))
        AssertEquals("9", MathHelper.IntToHex(9))
        AssertEquals("A", MathHelper.IntToHex(10))
        AssertEquals("B", MathHelper.IntToHex(11))
        AssertEquals("C", MathHelper.IntToHex(12))
        AssertEquals("D", MathHelper.IntToHex(13))
        AssertEquals("E", MathHelper.IntToHex(14))
        AssertEquals("F", MathHelper.IntToHex(15))
        AssertEquals("A2DE", MathHelper.IntToHex(41694))
    End
End

Strict

Private

Import bono

Public

Class MathHelperTest Extends TestCase
    Method TestRoundUp:Void()
       AssertEquals(1, MathHelper.Round(0.5))
       AssertEquals(1, MathHelper.Round(0.6))
       AssertEquals(1, MathHelper.Round(0.7))
       AssertEquals(1, MathHelper.Round(0.8))
       AssertEquals(1, MathHelper.Round(0.9))
    End

    Method TestRoundUpWithNumberGreaterThanOne:Void()
       AssertEquals(10, MathHelper.Round(9.5))
       AssertEquals(10, MathHelper.Round(9.6))
       AssertEquals(10, MathHelper.Round(9.7))
       AssertEquals(10, MathHelper.Round(9.8))
       AssertEquals(10, MathHelper.Round(9.9))
    End

    Method TestRoundDown:Void()
       AssertEquals(0, MathHelper.Round(0.4))
       AssertEquals(0, MathHelper.Round(0.3))
       AssertEquals(0, MathHelper.Round(0.2))
       AssertEquals(0, MathHelper.Round(0.1))
    End

    Method TestRoundDownWithNumberGreaterThanOne:Void()
       AssertEquals(9, MathHelper.Round(9.4))
       AssertEquals(9, MathHelper.Round(9.3))
       AssertEquals(9, MathHelper.Round(9.2))
       AssertEquals(9, MathHelper.Round(9.1))
    End

    Method TestRoundAlreadyRounded:Void()
        AssertEquals(1, MathHelper.Round(1))
    End

    Method TestHextToInt:Void()
        AssertEquals(0, MathHelper.HexToInt("0"))
        AssertEquals(1, MathHelper.HexToInt("1"))
        AssertEquals(2, MathHelper.HexToInt("2"))
        AssertEquals(3, MathHelper.HexToInt("3"))
        AssertEquals(4, MathHelper.HexToInt("4"))
        AssertEquals(5, MathHelper.HexToInt("5"))
        AssertEquals(6, MathHelper.HexToInt("6"))
        AssertEquals(7, MathHelper.HexToInt("7"))
        AssertEquals(8, MathHelper.HexToInt("8"))
        AssertEquals(9, MathHelper.HexToInt("9"))
        AssertEquals(10, MathHelper.HexToInt("A"))
        AssertEquals(11, MathHelper.HexToInt("B"))
        AssertEquals(12, MathHelper.HexToInt("C"))
        AssertEquals(13, MathHelper.HexToInt("D"))
        AssertEquals(14, MathHelper.HexToInt("E"))
        AssertEquals(15, MathHelper.HexToInt("F"))
        AssertEquals(48879, MathHelper.HexToInt("BEEF"))
    End

    Method TestIntToHex:Void()
        AssertEquals("0", MathHelper.IntToHex(0))
        AssertEquals("1", MathHelper.IntToHex(1))
        AssertEquals("2", MathHelper.IntToHex(2))
        AssertEquals("3", MathHelper.IntToHex(3))
        AssertEquals("4", MathHelper.IntToHex(4))
        AssertEquals("5", MathHelper.IntToHex(5))
        AssertEquals("6", MathHelper.IntToHex(6))
        AssertEquals("7", MathHelper.IntToHex(7))
        AssertEquals("8", MathHelper.IntToHex(8))
        AssertEquals("9", MathHelper.IntToHex(9))
        AssertEquals("A", MathHelper.IntToHex(10))
        AssertEquals("B", MathHelper.IntToHex(11))
        AssertEquals("C", MathHelper.IntToHex(12))
        AssertEquals("D", MathHelper.IntToHex(13))
        AssertEquals("E", MathHelper.IntToHex(14))
        AssertEquals("F", MathHelper.IntToHex(15))
        AssertEquals("A2DE", MathHelper.IntToHex(41694))
    End
End


optimized some Vector2D instantiation away

Strict

Private

Import bono

Public

Class ActionMoveTo Extends BaseAction
    Private

    Field obj:Positionable
    Field endPos:Vector2D
    Field startPos := New Vector2D()
    Field distance := New Vector2D()
    Field currentPos := New Vector2D()

    Public

    Method New()
        Throw New InvalidConstructorException("use New(Positionable, Vector2D)")
    End

    Method New(obj:Positionable, pos:Vector2D)
        Self.obj = obj
        Self.endPos = pos
    End

    Method OnActionStart:Void()
        Super.OnActionStart()

        startPos.Set(obj.GetPosition())
        distance.Set(endPos).Sub(startPos)
    End

    Method OnActionUpdate:Void(progress:Float)
        currentPos.Set(distance).Mul(progress).Add(startPos)
        obj.GetPosition().Set(currentPos)
    End
End

Strict

Private

Import bono

Public

Class ActionMoveTo Extends BaseAction
    Private

    Field endPos:Vector2D
    Field startPos:Vector2D
    Field distance:Vector2D
    Field obj:Positionable

    Public

    Method New()
        Throw New InvalidConstructorException("use New(Positionable, Vector2D)")
    End

    Method New(obj:Positionable, pos:Vector2D)
        Self.obj = obj
        Self.endPos = pos
    End

    Method OnActionStart:Void()
        Super.OnActionStart()

        startPos = obj.GetPosition().Copy()
        distance = endPos.Copy().Sub(startPos)
    End

    Method OnActionUpdate:Void(progress:Float)
        obj.GetPosition().Set(startPos.Copy().Add(distance.Copy().Mul(progress)))
    End
End


Increased maximum emit count to 100.

Increased maximum emit count to 100.

--HG--
extra : convert_revision : svn%3Ae934d3cd-0d57-f34a-94d0-9e8d31ae1c29/trunk%40295

Strict

Import mojo
Import diddy

Global testScreen:ParticleTestScreen

Function Main:Int()
	game = New ParticleTestApp
	Return 0
End

Class ParticleTestApp Extends DiddyApp
	Method OnCreate:Int()
		Super.OnCreate()
		drawFPSOn = True
		testScreen = New ParticleTestScreen
		testScreen.PreStart()
		Return 0
	End
End

Class ParticleTestScreen Extends Screen
	Field ps:ParticleSystem
	Field pg:ParticleGroup
	Field e:Emitter
	Field e2:Emitter
	Field f:Force
	Field pf:PointForce
	Field speck:Image

	Field emitting:Bool = True
	Field emitCount:Int = 3
	Field rendering:Bool = True

	Method New()
		name = "Particle System Test"
		speck = LoadImage("speck.png",,Image.MidHandle)
		CreateSystem()
	End

	Method Start:Void()
		game.screenFade.Start(50, False)
	End

	Method Render:Void()
		Cls
		If rendering Then ps.Render()
		SetColor(255,255,255)
	End

	Method DebugRender:Void()
		SetColor(255,255,255)
		SetAlpha(1)
#If TARGET="android" Or TARGET="ios" Then
		DrawText("Tap top half to toggle emitter, bottom half to toggle rendering",0,20)
		DrawText("pg.AliveParticles="+pg.AliveParticles,0,35)
#Else
		DrawText("Space: toggle emitter, R: toggle rendering",0,20)
		DrawText("Hold Shift: Cursor repels, Hold Control: Cursor attracts",0,35)
		DrawText("Up/Down arrows increase/decrease emit count from 1-100. Current: "+emitCount,0,50)
		DrawText("pg.AliveParticles="+pg.AliveParticles,0,65)
#End
	End

	Method Update:Void()
		pf.X = game.mouseX
		pf.Y = game.mouseY
		If KeyDown(KEY_CONTROL) Then
			pf.Acceleration = Abs(pf.Acceleration)
			pf.Enabled = True
			f.Enabled = False
		Elseif KeyDown(KEY_SHIFT) Then
			pf.Acceleration = -Abs(pf.Acceleration)
			pf.Enabled = True
			f.Enabled = False
		Else
			pf.Enabled = False
			f.Enabled = True
		End

		If KeyHit(KEY_SPACE) Then emitting = Not emitting
		If KeyHit(KEY_R) Then rendering = Not rendering

		If TouchHit() Then
			If TouchY() < SCREEN_HEIGHT2 Then
				emitting = Not emitting
			Else
				rendering = Not rendering
			End
		End

		If KeyHit(KEY_UP) Then emitCount+=1
		If KeyHit(KEY_DOWN) Then emitCount-=1
		If emitCount < 1 Then emitCount = 1
		If emitCount > 100 Then emitCount = 100

		If emitting Then
			e.EmitAt(emitCount, game.mouseX, game.mouseY)
		End
		ps.Update(dt.frametime)
		If KeyHit(KEY_ESCAPE) Then
			game.screenFade.Start(50, True)
			game.nextScreen = game.exitScreen
		End
	End

	Method CreateSystem:Void()
		Local parser:XMLParser = New XMLParser
		Local doc:XMLDocument = parser.ParseFile("psystem.xml")
		ps = New ParticleSystem(doc)
		pg = ps.GetGroup("group1")
		f = ConstantForce(pg.GetForce("gravity"))
		pf = PointForce(pg.GetForce("point"))
		e = ps.GetEmitter("emit1")
		e.ParticleImage = speck
		e2 = ps.GetEmitter("emit2")
		e.AddDeathEmitter(e2, 0.05)
	End
End

Strict

Import mojo
Import diddy

Global testScreen:ParticleTestScreen

Function Main:Int()
	game = New ParticleTestApp
	Return 0
End

Class ParticleTestApp Extends DiddyApp
	Method OnCreate:Int()
		Super.OnCreate()
		drawFPSOn = True
		testScreen = New ParticleTestScreen
		testScreen.PreStart()
		Return 0
	End
End

Class ParticleTestScreen Extends Screen
	Field ps:ParticleSystem
	Field pg:ParticleGroup
	Field e:Emitter
	Field e2:Emitter
	Field f:Force
	Field pf:PointForce
	Field speck:Image

	Field emitting:Bool = True
	Field emitCount:Int = 3
	Field rendering:Bool = True

	Method New()
		name = "Particle System Test"
		speck = LoadImage("speck.png",,Image.MidHandle)
		CreateSystem()
	End

	Method Start:Void()
		game.screenFade.Start(50, False)
	End

	Method Render:Void()
		Cls
		If rendering Then ps.Render()
		SetColor(255,255,255)
	End

	Method DebugRender:Void()
		SetColor(255,255,255)
		SetAlpha(1)
#If TARGET="android" Or TARGET="ios" Then
		DrawText("Tap top half to toggle emitter, bottom half to toggle rendering",0,20)
		DrawText("pg.AliveParticles="+pg.AliveParticles,0,35)
#Else
		DrawText("Space: toggle emitter, R: toggle rendering",0,20)
		DrawText("Hold Shift: Cursor repels, Hold Control: Cursor attracts",0,35)
		DrawText("Up/Down arrows increase/decrease emit count from 1-20. Current: "+emitCount,0,50)
		DrawText("pg.AliveParticles="+pg.AliveParticles,0,65)
#End
	End

	Method Update:Void()
		pf.X = game.mouseX
		pf.Y = game.mouseY
		If KeyDown(KEY_CONTROL) Then
			pf.Acceleration = Abs(pf.Acceleration)
			pf.Enabled = True
			f.Enabled = False
		Elseif KeyDown(KEY_SHIFT) Then
			pf.Acceleration = -Abs(pf.Acceleration)
			pf.Enabled = True
			f.Enabled = False
		Else
			pf.Enabled = False
			f.Enabled = True
		End

		If KeyHit(KEY_SPACE) Then emitting = Not emitting
		If KeyHit(KEY_R) Then rendering = Not rendering

		If TouchHit() Then
			If TouchY() < SCREEN_HEIGHT2 Then
				emitting = Not emitting
			Else
				rendering = Not rendering
			End
		End

		If KeyHit(KEY_UP) Then emitCount+=1
		If KeyHit(KEY_DOWN) Then emitCount-=1
		If emitCount < 1 Then emitCount = 1
		If emitCount > 20 Then emitCount = 20

		If emitting Then
			e.EmitAt(emitCount, game.mouseX, game.mouseY)
		End
		ps.Update(dt.frametime)
		If KeyHit(KEY_ESCAPE) Then
			game.screenFade.Start(50, True)
			game.nextScreen = game.exitScreen
		End
	End

	Method CreateSystem:Void()
		Local parser:XMLParser = New XMLParser
		Local doc:XMLDocument = parser.ParseFile("psystem.xml")
		ps = New ParticleSystem(doc)
		pg = ps.GetGroup("group1")
		f = ConstantForce(pg.GetForce("gravity"))
		pf = PointForce(pg.GetForce("point"))
		e = ps.GetEmitter("emit1")
		e.ParticleImage = speck
		e2 = ps.GetEmitter("emit2")
		e.AddDeathEmitter(e2, 0.05)
	End
End


Remove useless Text.recalculateSize flag

Strict

Private

Import baseobject
Import director
Import mojo
Import vector2d
Import vendor.angelfont

Public

Class Text Extends BaseObject
    Private

    Field _align:Int = ALIGN_LEFT
    Field _text:String = ""
    Field name:String
    Global angelFontStore:StringMap<AngelFont> = New StringMap<AngelFont>()

    Public

    Const ALIGN_LEFT:Int = AngelFont.ALIGN_LEFT
    Const ALIGN_CENTER:Int = AngelFont.ALIGN_CENTER
    Const ALIGN_RIGHT:Int = AngelFont.ALIGN_RIGHT

    Method New(name:String, pos:Vector2D=Null)
        If pos = Null Then pos = New Vector2D(0, 0)

        Self.name = name
        Self.pos = pos
    End

    Method OnCreate:Void(director:Director)
        Super.OnCreate(director)

        If Not angelFontStore.Contains(name)
            Print "LOADED"
            angelFontStore.Set(name, New AngelFont())
            angelFontStore.Get(name).LoadFont(name)
        End

        text = _text
    End

    Method OnRender:Void()
        If color Then color.Activate()
        angelFont.DrawText(_text, pos.x, pos.y, _align)
        If color Then color.Deactivate()
    End

    Method TextWidth:Int(char:String)
        Return angelFont.TextWidth(char)
    End

    Method TextHeight:Int(char:String)
        Return angelFont.TextHeight(char)
    End

    Method text:Void(newText:String) Property
        _text = newText
        If Not angelFont Then Return

        Local width:Float = angelFont.TextWidth(newText)
        Local height:Float = angelFont.TextHeight(newText)
        size = New Vector2D(width, height)
    End

    Method text:String() Property
        Return _text
    End

    Method align:Void(newAlign:Int) Property
        Select newAlign
        Case ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT
            _align = newAlign
        Default
            Error("Invalid align value specified.")
        End
    End

    Method align:Int() Property
        Return _align
    End

    Private

    Method angelFont:AngelFont() Property
        Return angelFontStore.Get(name)
    End
End

Strict

Private

Import baseobject
Import director
Import mojo
Import vector2d
Import vendor.angelfont

Public

Class Text Extends BaseObject
    Private

    Field _align:Int = ALIGN_LEFT
    Field _text:String
    Field name:String
    Field recalculateSize:Bool
    Global angelFontStore:StringMap<AngelFont> = New StringMap<AngelFont>()

    Public

    Const ALIGN_LEFT:Int = AngelFont.ALIGN_LEFT
    Const ALIGN_CENTER:Int = AngelFont.ALIGN_CENTER
    Const ALIGN_RIGHT:Int = AngelFont.ALIGN_RIGHT

    Method New(name:String, pos:Vector2D=Null)
        If pos = Null Then pos = New Vector2D(0, 0)

        Self.name = name
        Self.pos = pos
    End

    Method OnCreate:Void(director:Director)
        Super.OnCreate(director)

        If Not angelFontStore.Contains(name)
            Print "LOADED"
            angelFontStore.Set(name, New AngelFont())
            angelFontStore.Get(name).LoadFont(name)
        End

        If recalculateSize Then text = _text
    End

    Method OnRender:Void()
        If color Then color.Activate()
        angelFont.DrawText(_text, pos.x, pos.y, _align)
        If color Then color.Deactivate()
    End

    Method TextWidth:Int(char:String)
        Return angelFont.TextWidth(char)
    End

    Method TextHeight:Int(char:String)
        Return angelFont.TextHeight(char)
    End

    Method text:Void(newText:String) Property
        _text = newText
        If Not angelFont
            recalculateSize = True
            Return
        End

        Local width:Float = angelFont.TextWidth(newText)
        Local height:Float = angelFont.TextHeight(newText)
        size = New Vector2D(width, height)
        recalculateSize = False
    End

    Method text:String() Property
        Return _text
    End

    Method align:Void(newAlign:Int) Property
        Select newAlign
        Case ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT
            _align = newAlign
        Default
            Error("Invalid align value specified.")
        End
    End

    Method align:Int() Property
        Return _align
    End

    Private

    Method angelFont:AngelFont() Property
        Return angelFontStore.Get(name)
    End
End