Added MoveBy and MoveTo to SimpleDialog

Added MoveBy and MoveTo to SimpleDialog

#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

#Rem
Header: A collection of very simple GUI elements, usually for a basic menu system.
#End

Import diddy

Const VERTICAL:Int = 0
Const HORIZONTAL:Int = 1

#Rem
Summary: Provides a group of buttons that automatically handle mouseover images and sounds.
The buttons are automatically layed out horizontally or vertically, so only the menu itself needs to be positioned.
#End
Class SimpleMenu Extends List<SimpleButton>
	Field x:Float, y:Float
	Field buttonGap:Int = 0
	Field mouseOverName:String = ""
	Field clickedName:String = ""
	Field clearClickedName:Int = 1
	Field nextX:Int = 0
	Field nextY:Int = 0
	Field w:Int, h:Int
	Field addGap:Int = 0
	Field soundMouseOver:GameSound
	Field soundClick:GameSound
	Field useVirtualRes:Bool = False
	Field orientation:Int = VERTICAL

	Method New()
		Error "Please use a different constructor"
	End

#Rem
Summary: Creates a new SimpleMenu with the specified configuration.
#End
	Method New(soundMouseOverFile$, soundClickFile$, x:Int, y:Int, gap:Int, useVirtualRes:Bool, orientation:Int = VERTICAL)
		Init(soundMouseOverFile, soundClickFile, x, y, gap, useVirtualRes, orientation)
	End

#Rem
Summary: Initialises the menu (internal method).
Developers do not need to call this.
#End
	Method Init:Void(soundMouseOverFile:String="", soundClickFile:String="", x:Float, y:Float, gap:Int, useVirtualRes:Bool, orientation:Int)
		Self.Clear()
		Self.useVirtualRes = useVirtualRes
		Self.orientation = orientation
		Self.x = x
		Self.y = y
		nextX = x
		nextY = y
		Self.buttonGap = gap
		w = 0
		h = 0
		mouseOverName = ""
		clickedName = ""
		addGap = 0
		If soundMouseOverFile<>"" Then
			soundMouseOver = New GameSound
			soundMouseOver.Load(soundMouseOverFile)
		End
		If soundClickFile<>"" Then
			soundClick = New GameSound
			soundClick.Load(soundClickFile)
		End
	End

#Rem
Summary: Calculates and returns the minimum width of the menu required to fit all buttons at their current positions.
See [[CalcHeight]].
Developers do not need to call this.
#End
	Method CalcWidth:Int()
		Local left:Int=10000
		Local right:Int=-10000
		Local b:SimpleButton
		For b = EachIn Self
			If b.x < left Then left = b.x
			If b.x+b.image.w > right Then right = b.x + b.image.w
		Next
		w = right - left
		Return w
	End

#Rem
Summary: Calculates and returns the minimum height of the menu required to fit all buttons at their current positions.
See [[CalcWidth]].
Developers do not need to call this.
#End
	Method CalcHeight:Int()
		Local top:Int=10000
		Local bot:Int=-10000
		Local b:SimpleButton
		For b = EachIn Self
			If b.y < top Then top = b.y
			If b.y+b.image.h > bot Then bot = b.y + b.image.h
		Next
		h = bot - top
		Return h
	End

#Rem
Summary: Sets the X coordinate of the menu, moving all the buttons with it.
#End
	Method SetX:Void(thex#)
		CalcLeft()
		Local oldx# = x
		x = thex
		Local diff# = x - oldx
		Local b:SimpleButton
		For b = EachIn Self
			b.MoveBy(diff,0)
		Next
	End

#Rem
Summary: Sets the Y coordinate of the menu, moving all the buttons with it.
#End
	Method SetY:Void(they#)
		CalcTop()
		Local oldy# = y
		y = they
		Local diff# = y-oldy
		Local b:SimpleButton
		For b = EachIn Self
			b.MoveBy(0,diff)
		Next
	End

#Rem
Summary: Sets the x coordinate of the menu to be that of the leftmost button.
Developers do not need to call this.
#End
	Method CalcLeft:Void()
		x = 10000
		Local b:SimpleButton
		For b = EachIn Self
			If b.x <x Then x = b.x
		Next
	End

#Rem
Summary: Sets the y coordinate of the menu to be that of the topmost button.
Developers do not need to call this.
#End
	Method CalcTop:Void()
		y = 10000
		Local b:SimpleButton
		For b = EachIn Self
			If b.y < y Then y = b.y
		Next
	End

#Rem
Summary: Sets the x coordinate of the menu to be centred horizontally on the screen.
#End
	Method CentreHoriz:Void()
		CalcWidth()
		if useVirtualRes
			SetX((SCREEN_WIDTH-w)/2)
		Else
			SetX((DEVICE_WIDTH-w)/2)
		End
	End

#Rem
Summary: Sets the y coordinate of the menu to be centred vertically on the screen.
#End
	Method CentreVert:Void()
		CalcHeight()
		if useVirtualRes
			SetY((SCREEN_HEIGHT-h)/2)
		Else
			SetY((DEVICE_HEIGHT-h)/2)
		End
	End

#Rem
Summary: Sets the x and y coordinates of the menu to be centred both horizontally and vertically on the screen.
#End
	Method Centre:Void()
		CentreHoriz()
		CentreVert()
	End

#Rem
Summary: Sets the alpha for each button to the passed value.
#End
	Method SetMenuAlpha:Void(alpha:Float)
		Local b:SimpleButton
		For b = EachIn Self
			b.alpha = alpha
		Next
	End

#Rem
Summary: Creates a new [[SimpleButton]] and initialises it with the specified image, mouseover image, and name.
The button is added to the end of the menu and is positioned to fit the current menu orientation.
[code]
Local sb:SimpleButton = menu.AddButton(diddyGame.images.Find("newgame"), diddyGame.images.Find("newgameMO"), "New Game")
[/code]
#End
	Method AddButton:SimpleButton(buttonImage:GameImage, mouseOverFile:GameImage, name:String = "", drawText:Bool = False)
		Local b:SimpleButton = ProcessAddButton(buttonImage, mouseOverFile, name, drawText)
		If orientation = VERTICAL
			IncreaseHeight(b)
		Else
			IncreaseWidth(b)
		End
		Return b
	End

#Rem
Summary: Creates a new [[SimpleButton]] and initialises it with the specified image, mouseover image, and name.
The button is added to the end of the menu and is positioned to fit the current menu orientation.
[code]
Local sb:SimpleButton = menu.AddButton("newgame", "newgameMO", "New Game")
[/code]
#End
	Method AddButton:SimpleButton(buttonImageFile:String, mouseOverFile:String, name:String = "", drawText:Bool = False)
		Local b:SimpleButton = ProcessAddButton(buttonImageFile, mouseOverFile, name, drawText)
		If orientation = VERTICAL
			IncreaseHeight(b)
		Else
			IncreaseWidth(b)
		End
		Return b
	End

#Rem
Summary: Increases the height of the menu to fit the passed SimpleButton.
Developers do not need to call this.
#End
	Method IncreaseHeight:Void(b:SimpleButton)
		nextY = nextY + b.image.h + buttonGap

		h = h + b.image.h
		If addGap Then
			h = h + buttonGap
		Else
			addGap = 1
		End
	End

#Rem
Summary: Increases the width of the menu to fit the passed SimpleButton.
Developers do not need to call this.
#End
	Method IncreaseWidth:Void(b:SimpleButton)
		nextX = nextX + b.image.w + buttonGap

		w = w + b.image.w
		If addGap Then
			w = w + buttonGap
		Else
			addGap = 1
		End
	End

#Rem
Summary: Internal method to add a button to the menu.
Developers do not need to call this.
#End
	Method ProcessAddButton:SimpleButton(buttonImage:GameImage, mouseOver:GameImage, name:String, drawText:Bool = False)
		Local b:SimpleButton = New SimpleButton
		b.drawText = drawText
		b.name = StripAll(buttonImage.name.ToUpper())
		b.image = buttonImage
		b.image.SetHandle(0, 0)
		If mouseOver <> Null
			b.imageMouseOver = mouseOver
			b.imageMouseOver.SetHandle(0, 0)
		End
		b = ProcessButton(b, name)
		Return b
	End

#Rem
Summary: Internal method to add a button to the menu.
Developers do not need to call this.
#End
	Method ProcessAddButton:SimpleButton(buttonImageFile:String, mouseOverFile:String, name:String, drawText:Bool = False)
		Local b:SimpleButton = New SimpleButton
		b.drawText = drawText
		b.Load(buttonImageFile, mouseOverFile)
		b = ProcessButton(b, name)
		Return b
	End

#Rem
Summary: Internal method to add a button to the menu.
Developers do not need to call this.
#End
	Method ProcessButton:SimpleButton(b:SimpleButton, name:String)
		b.useVirtualRes = Self.useVirtualRes
		b.orientation = Self.orientation
		b.text = name
		If name <> "" Then b.name = name.ToUpper()
		If orientation = VERTICAL
			b.CentreX(nextY)
		Else
			b.MoveTo(nextX, nextY)
		End

		b.soundMouseOver = soundMouseOver
		b.soundClick = soundClick
		AddLast(b)
		Return b
	End

#Rem
Summary: Finds the button in the menu with the given name.
[code]
Local sb:SimpleButton = menu.Find("New Game")
[/code]
#End
	Method FindButton:SimpleButton(name:String)
		name = name.ToUpper()
		Local b:SimpleButton
		For b = EachIn Self
			If b.name = name Then Return b
		Next
		Return Null
	End

#Rem
Summary: Returns 1 if the button with the specified name was clicked since the last call to [[Update]].
[code]
menu.Update()
...
If menu.Clicked("New Game") Then
	' start a new game
ElseIf menu.Clicked("Quit") Then
	' exit the game
End
[/code]
#End
	Method Clicked:Int(name:String)
		name = name.ToUpper()
		If name = clickedName
			If clearClickedName Then clickedName = ""
			Return 1
		Else
			Return 0
		End
	End

#Rem
Summary: Determines whether the user clicked on a button, such that the next call to [[Clicked]] can test for it.
This should be called only once, towards the start of your [[Screen.Update]] implementation, or [[App.OnUpdate]].
#End
	Method Update:Int()
		If diddyGame.screenFade.active
			Return 0
		EndIf
		clickedName = ""
		Local b:SimpleButton
		For b = EachIn Self
			b.Update()
			If b.mouseOver Then mouseOverName = b.name
			If b.clicked Then clickedName = b.name
		Next
		Return 1
	End

#Rem
Summary: Delegates to [[Sprite.Precache]] on each of the buttons.
#End
	Method Precache:Void()
		For Local b:SimpleButton = EachIn Self
			b.Precache()
		Next
	End

#Rem
Summary: Delegates to [[SimpleButton.Draw]] on each of the buttons.
#End
	Method Draw:Void()
		For Local b:SimpleButton = EachIn Self
			b.Draw()
		Next
	End
End

#Rem
Summary: A Delegate so that the developer can override the drawing of text on widgets
#END
Class SimpleTextDrawDelegate Abstract
	Method Draw:Void(text:String, x:Float, y:Float)
	End
End

#Rem
Summary: Represents a button in a [[SimpleMenu]].
#End
Class SimpleButton Extends Sprite
	Field active:Int = 1
	Field clicked:Int = 0
	Field selected:Int = 0
	Field mouseOver:Int = 0
	Field disabled:Bool = False
	Field soundMouseOver:GameSound
	Field soundClick:GameSound
	Field imageMouseOver:GameImage
	Field imageSelected:GameImage
	Field imageSelectedMO:GameImage
	Field useVirtualRes:Bool = False
	Field orientation:Int = VERTICAL
	Field drawText:Bool
	Field text:String
	Field textDrawDelegate:SimpleTextDrawDelegate

#Rem
Summary: Delegates to [[Sprite.Precache]] if the button has a valid image.
Developers do not need to call this.
#End
	Method Precache:Void()
		If image<>null
			Super.Precache()
		End
	End

#Rem
Summary: Renders the button.
Developers do not need to call this.
#End
	Method Draw:Void()
		If active = 0 Then Return
		SetAlpha Self.alpha
		if mouseOver
			if selected And imageSelectedMO <> null Then
				DrawImage Self.imageSelectedMO.image, x, y
			Else
				DrawImage Self.imageMouseOver.image, x, y
			End
		ElseIf selected And imageSelected <> null Then
			DrawImage Self.imageSelected.image, x, y
		Else
			DrawImage Self.image.image, x, y
		EndIf
		If drawText
			If textDrawDelegate <> Null
				textDrawDelegate.Draw(text, x + Self.image.w2, y)
			Else
				DrawText(text, x + Self.image.w2, y + Self.image.h2, 0.5, 0.5)
			End
		End
		SetAlpha 1
	End

#Rem
Summary: Fires a button click.
Developers do not need to call this.
#End
	Method Click:Void()
		If clicked = 0
			clicked = 1
			If soundClick <> null
				soundClick.Play()
			End
		End
	End

#Rem
Summary: Centres the button horizontally within the screen, also setting the Y position to the passed value.
#End
	Method CentreX:Void(yCoord:Int)
		if useVirtualRes
			MoveTo((SCREEN_WIDTH-image.w)/2, yCoord)
		Else
			MoveTo((DEVICE_WIDTH-image.w)/2, yCoord)
		End

	End

#Rem
Summary: Centres the button vertically within the screen, also setting the X position to the passed value.
#End
	Method CentreY:Void(xCoord:Int)
		If useVirtualRes
			MoveTo((SCREEN_HEIGHT-image.h)/2, xCoord)
		Else
			MoveTo((DEVICE_HEIGHT-image.h)/2, xCoord)
		End
	End

#Rem
Summary: Moves the button by the passed number of pixels.
#End
	Method MoveBy:Void(dx:Float,dy:Float)
		x += dx
		y += dy
	End

#Rem
Summary: Moves the button to the exact specified location.
#End
	Method MoveTo:Void(dx:Float,dy:Float)
		x = dx
		y = dy
	End

#Rem
Summary: Loads the required images and sounds for the button.
Developers do not need to call this.
#End
	Method Load:Void(buttonImage:String, mouseOverImage:String = "", soundMouseOverFile:String="", soundClickFile:String="")
		Self.image = New GameImage
		image.Load(diddyGame.images.path + buttonImage, False)

		If mouseOverImage <> ""
			imageMouseOver = New GameImage
			imageMouseOver.Load(diddyGame.images.path + mouseOverImage, False)
		End

		name = StripAll(buttonImage.ToUpper())

		If soundMouseOverFile<>"" Then
			soundMouseOver = New GameSound
			soundMouseOver.Load(soundMouseOverFile)
		End
		If soundClickFile<>"" Then
			soundClick = New GameSound
			soundClick.Load(soundClickFile)
		End
	End

#Rem
Summary: Sets additional images so that the button can be used as a toggle button.
#End
	Method SetSelectedImage:Void(buttonImage:String, buttonImageMO:String = "")
		imageSelected = New GameImage
		imageSelected.Load(diddyGame.images.path + buttonImage, False)

		If buttonImageMO <> ""
			imageSelectedMO = New GameImage
			imageSelectedMO.Load(diddyGame.images.path + buttonImageMO, False)
		End
	End

#Rem
Summary: Updates the clicked status of the button, and plays mouseover sounds.
Developers only need to call this if they are using the button outside of a [[SimpleMenu]].
#End
	Method Update:Void()
		If active = 0 or disabled Then Return
		Local mx:Int = diddyGame.mouseX
		Local my:Int = diddyGame.mouseY
		If not useVirtualRes
			mx = MouseX()
			my = MouseY()
		End
		If mx >= x And mx < x+image.w And my >= y And my < y+image.h Then
			If mouseOver = 0
				if soundMouseOver <> null
					soundMouseOver.Play()
				End
			End
			mouseOver = 1
			If MouseHit() Then
				Click()
			Else
				clicked = 0
			End
		Else
			mouseOver = 0
			clicked = 0
		End
	End
End

#Rem
Summary: Represents a slider control, often used for volume levels.
#End
Class SimpleSlider Extends Sprite
	Field active:Int
	Field dotImage:GameImage
	Field dotX:Int, dotY:Int
	Field value:Int
	Field border:Int=0
	Field borderY:Int=5
	Field useVirtualRes:Bool = False

#Rem
Summary: Creates a new [[SimpleSlider]] with the specified configuration.
#End
	Method New(barFile:String, dotFile:String, x:Int, y:int, border:int = 0, name:String="", borderY:int=5, useVirtualRes:Bool = True)
		Self.image = New GameImage
		Self.useVirtualRes = useVirtualRes

		image.Load(diddyGame.images.path + barFile, False)
		name = StripAll(barFile.ToUpper())

		dotImage = New GameImage
		dotImage.Load(diddyGame.images.path + dotFile, False)
		dotImage.name = StripAll(dotFile.ToUpper())

		Self.x = x
		Self.y = y
		Self.border = border
		Self.borderY = borderY

		Self.SetValue(50)
		Self.dotY = y-3
		Self.active = 1
	End

#Rem
Summary: Sets the value of the slider (between 0 and 100 inclusive) and updates the position of the bar.
#End
	Method SetValue:Void(toSet:int)
		value = toSet
		If toSet < 0 Then value = 0
		If toSet > 100 Then value = 100
		Local percent:Float = value/100.0
		dotX = x + border + (percent * (image.w - (border * 2))) - dotImage.w2
	End

#Rem
Summary: Updates the value of the slider if the user has dragged somewhere on it.
This should be called only once per slider, towards the start of your [[Screen.Update]] implementation, or [[App.OnUpdate]].
Returns 1 if the slider changed value since the last frame, otherwise 0.
#End
	Method Update:Int()
		Local change:Int=0
		If active
			Local buffer:int = 10
			Local mx:Int = diddyGame.mouseX
			Local my:Int = diddyGame.mouseY
			if not useVirtualRes
				mx = MouseX()
				my = MouseY()
			End
			If mx >= x - buffer And mx < x + image.w + buffer And my >= y - borderY And my < y + image.h + borderY
				If MouseDown(MOUSE_LEFT)
					If mx <= x+border
						SetValue(0)
						change = 1
					ElseIf mx >= x+image.w-border
						SetValue(100)
						change = 1
					Else
						Local d:Float = mx - x - border
						Local p:Float = d/(image.w-(border*2))
						SetValue(Round(p*100))
						change = 1
					End
				End
			End
		End
		Return change
	End

#Rem
Summary: Renders the slider.
#End
	Method Draw:Void()
		If active
			DrawImage(image.image,x,y)
			DrawImage(dotImage.image,dotX,dotY)
		End
	End
End

#Rem
Summary: Provides a dialog which a menu can be added to
#End
Class SimpleDialog
	Field alpha:Float = 0
	Field menu:SimpleMenu
	Field title:String
	Field image:GameImage
	Field show:Bool
	Field titleX:Float
	Field titleY:Float
	Field x:Float
	Field y:Float
	Field fadeInSpeed:Float
	Field fadeOutSpeed:Float
	Field titleDrawDelegate:SimpleTextDrawDelegate
	Field alphaControl:Float
	Field text:String
	Field textX:Float
	Field textY:Float
	Field textDrawDelegate:SimpleTextDrawDelegate
	Field textColor:Int[3]

#Rem
Summary: Creates a new [[SimpleDialog]] with the specified configuration.
#End
	Method New(menu:SimpleMenu, image:GameImage)
		Self.alpha = 0
		Self.menu = menu
		Self.image = image
		x = SCREEN_WIDTH2
		y = SCREEN_HEIGHT2
		titleX = SCREEN_WIDTH2
		titleY = 40
		fadeInSpeed = 0.05
		fadeOutSpeed = 0.08
		alphaControl = 0.4
		textX = SCREEN_WIDTH2
		textY = titleY + 50
		textColor[0] = 255
		textColor[1] = 255
		textColor[2] = 255
	End

#Rem
Summary: Updates the dialog, controls the alpha and menu (menu is only usable if alpha is greater than alphaControl
#End
	Method Update:Void()
		If show
			If alpha < 1
				alpha += fadeInSpeed * dt.delta
			Else
				alpha = 1
			End
			If menu
				menu.SetMenuAlpha(alpha)
				If alpha > alphaControl Then
					menu.Update()
				End
			End
		Else
			If alpha > 0
				alpha -= fadeOutSpeed * dt.delta
				If alpha < 0 Then alpha = 0
				If menu Then menu.SetMenuAlpha(alpha)
			Else
				alpha = 0
				show = False
			End
		End
	End

#Rem
Summary: Set the colour of the text
#END
	Method SetTextColor:Void(r:Int, g:Int, b:Int)
		textColor[0] = r
		textColor[1] = g
		textColor[2] = b
	End

#Rem
Summary: Renders the dialog.
#End
	Method Draw:Void()
		If alpha > 0
			SetAlpha alpha
			image.Draw(x, y)
			If titleDrawDelegate <> Null Then
				titleDrawDelegate.Draw(title, titleX, titleY)
			Else
				DrawText(title, titleX, titleY, 0.5, 0.5)
			End
			SetColor(textColor[0], textColor[1], textColor[2])
			If textDrawDelegate <> Null Then
				textDrawDelegate.Draw(text, textX, textY)
			Else
				DrawText(text, textX, textY, 0.5, 0.5)
			End
			SetColor(255, 255, 255)
			If menu Then menu.Draw()

			SetAlpha 1
		End
	End

#Rem
Summary: Moves the dialong by the set amounts along with text and menus
#End
	Method MoveBy:Void(x:Float, y:Float, moveButtons:Bool = True)
		Self.x += x
		Self.y += y
		Self.textX += x
		Self.textY += y
		Self.titleX += x
		Self.titleY += y

		If moveButtons
			If menu
				For Local b:SimpleButton = EachIn Self.menu
					b.MoveBy(x, y)
				Next
			End
		End
	End

	Method MoveTo:Void(newX:Float, newY:Float, moveButtons:Bool = True)
		Local oldx:Float = x
		Local oldy:Float = y

		Self.x = newX
		Self.y = newY

		Local diffX:Float = x - oldx
		Local diffY:Float = y - oldy

		Self.textX += diffX
		Self.textY += diffY
		Self.titleX += diffX
		Self.titleY += diffY

		If moveButtons
			If menu
				For Local b:SimpleButton = EachIn Self.menu
					b.MoveBy(diffX, diffY)
				Next
			End
		End
	End
End

#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

#Rem
Header: A collection of very simple GUI elements, usually for a basic menu system.
#End

Import diddy

Const VERTICAL:Int = 0
Const HORIZONTAL:Int = 1

#Rem
Summary: Provides a group of buttons that automatically handle mouseover images and sounds.
The buttons are automatically layed out horizontally or vertically, so only the menu itself needs to be positioned.
#End
Class SimpleMenu Extends List<SimpleButton>
	Field x:Float, y:Float
	Field buttonGap:Int = 0
	Field mouseOverName:String = ""
	Field clickedName:String = ""
	Field clearClickedName:Int = 1
	Field nextX:Int = 0
	Field nextY:Int = 0
	Field w:Int, h:Int
	Field addGap:Int = 0
	Field soundMouseOver:GameSound
	Field soundClick:GameSound
	Field useVirtualRes:Bool = False
	Field orientation:Int = VERTICAL

	Method New()
		Error "Please use a different constructor"
	End

#Rem
Summary: Creates a new SimpleMenu with the specified configuration.
#End
	Method New(soundMouseOverFile$, soundClickFile$, x:Int, y:Int, gap:Int, useVirtualRes:Bool, orientation:Int = VERTICAL)
		Init(soundMouseOverFile, soundClickFile, x, y, gap, useVirtualRes, orientation)
	End

#Rem
Summary: Initialises the menu (internal method).
Developers do not need to call this.
#End
	Method Init:Void(soundMouseOverFile:String="", soundClickFile:String="", x:Float, y:Float, gap:Int, useVirtualRes:Bool, orientation:Int)
		Self.Clear()
		Self.useVirtualRes = useVirtualRes
		Self.orientation = orientation
		Self.x = x
		Self.y = y
		nextX = x
		nextY = y
		Self.buttonGap = gap
		w = 0
		h = 0
		mouseOverName = ""
		clickedName = ""
		addGap = 0
		If soundMouseOverFile<>"" Then
			soundMouseOver = New GameSound
			soundMouseOver.Load(soundMouseOverFile)
		End
		If soundClickFile<>"" Then
			soundClick = New GameSound
			soundClick.Load(soundClickFile)
		End
	End

#Rem
Summary: Calculates and returns the minimum width of the menu required to fit all buttons at their current positions.
See [[CalcHeight]].
Developers do not need to call this.
#End
	Method CalcWidth:Int()
		Local left:Int=10000
		Local right:Int=-10000
		Local b:SimpleButton
		For b = EachIn Self
			If b.x < left Then left = b.x
			If b.x+b.image.w > right Then right = b.x + b.image.w
		Next
		w = right - left
		Return w
	End

#Rem
Summary: Calculates and returns the minimum height of the menu required to fit all buttons at their current positions.
See [[CalcWidth]].
Developers do not need to call this.
#End
	Method CalcHeight:Int()
		Local top:Int=10000
		Local bot:Int=-10000
		Local b:SimpleButton
		For b = EachIn Self
			If b.y < top Then top = b.y
			If b.y+b.image.h > bot Then bot = b.y + b.image.h
		Next
		h = bot - top
		Return h
	End

#Rem
Summary: Sets the X coordinate of the menu, moving all the buttons with it.
#End
	Method SetX:Void(thex#)
		CalcLeft()
		Local oldx# = x
		x = thex
		Local diff# = x - oldx
		Local b:SimpleButton
		For b = EachIn Self
			b.MoveBy(diff,0)
		Next
	End

#Rem
Summary: Sets the Y coordinate of the menu, moving all the buttons with it.
#End
	Method SetY:Void(they#)
		CalcTop()
		Local oldy# = y
		y = they
		Local diff# = y-oldy
		Local b:SimpleButton
		For b = EachIn Self
			b.MoveBy(0,diff)
		Next
	End

#Rem
Summary: Sets the x coordinate of the menu to be that of the leftmost button.
Developers do not need to call this.
#End
	Method CalcLeft:Void()
		x = 10000
		Local b:SimpleButton
		For b = EachIn Self
			If b.x <x Then x = b.x
		Next
	End

#Rem
Summary: Sets the y coordinate of the menu to be that of the topmost button.
Developers do not need to call this.
#End
	Method CalcTop:Void()
		y = 10000
		Local b:SimpleButton
		For b = EachIn Self
			If b.y < y Then y = b.y
		Next
	End

#Rem
Summary: Sets the x coordinate of the menu to be centred horizontally on the screen.
#End
	Method CentreHoriz:Void()
		CalcWidth()
		if useVirtualRes
			SetX((SCREEN_WIDTH-w)/2)
		Else
			SetX((DEVICE_WIDTH-w)/2)
		End
	End

#Rem
Summary: Sets the y coordinate of the menu to be centred vertically on the screen.
#End
	Method CentreVert:Void()
		CalcHeight()
		if useVirtualRes
			SetY((SCREEN_HEIGHT-h)/2)
		Else
			SetY((DEVICE_HEIGHT-h)/2)
		End
	End

#Rem
Summary: Sets the x and y coordinates of the menu to be centred both horizontally and vertically on the screen.
#End
	Method Centre:Void()
		CentreHoriz()
		CentreVert()
	End

#Rem
Summary: Sets the alpha for each button to the passed value.
#End
	Method SetMenuAlpha:Void(alpha:Float)
		Local b:SimpleButton
		For b = EachIn Self
			b.alpha = alpha
		Next
	End

#Rem
Summary: Creates a new [[SimpleButton]] and initialises it with the specified image, mouseover image, and name.
The button is added to the end of the menu and is positioned to fit the current menu orientation.
[code]
Local sb:SimpleButton = menu.AddButton(diddyGame.images.Find("newgame"), diddyGame.images.Find("newgameMO"), "New Game")
[/code]
#End
	Method AddButton:SimpleButton(buttonImage:GameImage, mouseOverFile:GameImage, name:String = "", drawText:Bool = False)
		Local b:SimpleButton = ProcessAddButton(buttonImage, mouseOverFile, name, drawText)
		If orientation = VERTICAL
			IncreaseHeight(b)
		Else
			IncreaseWidth(b)
		End
		Return b
	End

#Rem
Summary: Creates a new [[SimpleButton]] and initialises it with the specified image, mouseover image, and name.
The button is added to the end of the menu and is positioned to fit the current menu orientation.
[code]
Local sb:SimpleButton = menu.AddButton("newgame", "newgameMO", "New Game")
[/code]
#End
	Method AddButton:SimpleButton(buttonImageFile:String, mouseOverFile:String, name:String = "", drawText:Bool = False)
		Local b:SimpleButton = ProcessAddButton(buttonImageFile, mouseOverFile, name, drawText)
		If orientation = VERTICAL
			IncreaseHeight(b)
		Else
			IncreaseWidth(b)
		End
		Return b
	End

#Rem
Summary: Increases the height of the menu to fit the passed SimpleButton.
Developers do not need to call this.
#End
	Method IncreaseHeight:Void(b:SimpleButton)
		nextY = nextY + b.image.h + buttonGap

		h = h + b.image.h
		If addGap Then
			h = h + buttonGap
		Else
			addGap = 1
		End
	End

#Rem
Summary: Increases the width of the menu to fit the passed SimpleButton.
Developers do not need to call this.
#End
	Method IncreaseWidth:Void(b:SimpleButton)
		nextX = nextX + b.image.w + buttonGap

		w = w + b.image.w
		If addGap Then
			w = w + buttonGap
		Else
			addGap = 1
		End
	End

#Rem
Summary: Internal method to add a button to the menu.
Developers do not need to call this.
#End
	Method ProcessAddButton:SimpleButton(buttonImage:GameImage, mouseOver:GameImage, name:String, drawText:Bool = False)
		Local b:SimpleButton = New SimpleButton
		b.drawText = drawText
		b.name = StripAll(buttonImage.name.ToUpper())
		b.image = buttonImage
		b.image.SetHandle(0, 0)
		If mouseOver <> Null
			b.imageMouseOver = mouseOver
			b.imageMouseOver.SetHandle(0, 0)
		End
		b = ProcessButton(b, name)
		Return b
	End

#Rem
Summary: Internal method to add a button to the menu.
Developers do not need to call this.
#End
	Method ProcessAddButton:SimpleButton(buttonImageFile:String, mouseOverFile:String, name:String, drawText:Bool = False)
		Local b:SimpleButton = New SimpleButton
		b.drawText = drawText
		b.Load(buttonImageFile, mouseOverFile)
		b = ProcessButton(b, name)
		Return b
	End

#Rem
Summary: Internal method to add a button to the menu.
Developers do not need to call this.
#End
	Method ProcessButton:SimpleButton(b:SimpleButton, name:String)
		b.useVirtualRes = Self.useVirtualRes
		b.orientation = Self.orientation
		b.text = name
		If name <> "" Then b.name = name.ToUpper()
		If orientation = VERTICAL
			b.CentreX(nextY)
		Else
			b.MoveTo(nextX, nextY)
		End

		b.soundMouseOver = soundMouseOver
		b.soundClick = soundClick
		AddLast(b)
		Return b
	End

#Rem
Summary: Finds the button in the menu with the given name.
[code]
Local sb:SimpleButton = menu.Find("New Game")
[/code]
#End
	Method FindButton:SimpleButton(name:String)
		name = name.ToUpper()
		Local b:SimpleButton
		For b = EachIn Self
			If b.name = name Then Return b
		Next
		Return Null
	End

#Rem
Summary: Returns 1 if the button with the specified name was clicked since the last call to [[Update]].
[code]
menu.Update()
...
If menu.Clicked("New Game") Then
	' start a new game
ElseIf menu.Clicked("Quit") Then
	' exit the game
End
[/code]
#End
	Method Clicked:Int(name:String)
		name = name.ToUpper()
		If name = clickedName
			If clearClickedName Then clickedName = ""
			Return 1
		Else
			Return 0
		End
	End

#Rem
Summary: Determines whether the user clicked on a button, such that the next call to [[Clicked]] can test for it.
This should be called only once, towards the start of your [[Screen.Update]] implementation, or [[App.OnUpdate]].
#End
	Method Update:Int()
		If diddyGame.screenFade.active
			Return 0
		EndIf
		clickedName = ""
		Local b:SimpleButton
		For b = EachIn Self
			b.Update()
			If b.mouseOver Then mouseOverName = b.name
			If b.clicked Then clickedName = b.name
		Next
		Return 1
	End

#Rem
Summary: Delegates to [[Sprite.Precache]] on each of the buttons.
#End
	Method Precache:Void()
		For Local b:SimpleButton = EachIn Self
			b.Precache()
		Next
	End

#Rem
Summary: Delegates to [[SimpleButton.Draw]] on each of the buttons.
#End
	Method Draw:Void()
		For Local b:SimpleButton = EachIn Self
			b.Draw()
		Next
	End
End

#Rem
Summary: A Delegate so that the developer can override the drawing of text on widgets
#END
Class SimpleTextDrawDelegate
	Method Draw:Void(text:String, x:Float, y:Float)
	End
End

#Rem
Summary: Represents a button in a [[SimpleMenu]].
#End
Class SimpleButton Extends Sprite
	Field active:Int = 1
	Field clicked:Int = 0
	Field selected:Int = 0
	Field mouseOver:Int = 0
	Field disabled:Bool = False
	Field soundMouseOver:GameSound
	Field soundClick:GameSound
	Field imageMouseOver:GameImage
	Field imageSelected:GameImage
	Field imageSelectedMO:GameImage
	Field useVirtualRes:Bool = False
	Field orientation:Int = VERTICAL
	Field drawText:Bool
	Field text:String
	Field textDrawDelegate:SimpleTextDrawDelegate

#Rem
Summary: Delegates to [[Sprite.Precache]] if the button has a valid image.
Developers do not need to call this.
#End
	Method Precache:Void()
		If image<>null
			Super.Precache()
		End
	End

#Rem
Summary: Renders the button.
Developers do not need to call this.
#End
	Method Draw:Void()
		If active = 0 Then Return
		SetAlpha Self.alpha
		if mouseOver
			if selected And imageSelectedMO <> null Then
				DrawImage Self.imageSelectedMO.image, x, y
			Else
				DrawImage Self.imageMouseOver.image, x, y
			End
		ElseIf selected And imageSelected <> null Then
			DrawImage Self.imageSelected.image, x, y
		Else
			DrawImage Self.image.image, x, y
		EndIf
		If drawText
			If textDrawDelegate <> Null
				textDrawDelegate.Draw(text, x + Self.image.w2, y)
			Else
				DrawText(text, x + Self.image.w2, y + Self.image.h2, 0.5, 0.5)
			End
		End
		SetAlpha 1
	End

#Rem
Summary: Fires a button click.
Developers do not need to call this.
#End
	Method Click:Void()
		If clicked = 0
			clicked = 1
			If soundClick <> null
				soundClick.Play()
			End
		End
	End

#Rem
Summary: Centres the button horizontally within the screen, also setting the Y position to the passed value.
#End
	Method CentreX:Void(yCoord:Int)
		if useVirtualRes
			MoveTo((SCREEN_WIDTH-image.w)/2, yCoord)
		Else
			MoveTo((DEVICE_WIDTH-image.w)/2, yCoord)
		End

	End

#Rem
Summary: Centres the button vertically within the screen, also setting the X position to the passed value.
#End
	Method CentreY:Void(xCoord:Int)
		If useVirtualRes
			MoveTo((SCREEN_HEIGHT-image.h)/2, xCoord)
		Else
			MoveTo((DEVICE_HEIGHT-image.h)/2, xCoord)
		End
	End

#Rem
Summary: Moves the button by the passed number of pixels.
#End
	Method MoveBy:Void(dx:Float,dy:Float)
		x += dx
		y += dy
	End

#Rem
Summary: Moves the button to the exact specified location.
#End
	Method MoveTo:Void(dx:Float,dy:Float)
		x = dx
		y = dy
	End

#Rem
Summary: Loads the required images and sounds for the button.
Developers do not need to call this.
#End
	Method Load:Void(buttonImage:String, mouseOverImage:String = "", soundMouseOverFile:String="", soundClickFile:String="")
		Self.image = New GameImage
		image.Load(diddyGame.images.path + buttonImage, False)

		If mouseOverImage <> ""
			imageMouseOver = New GameImage
			imageMouseOver.Load(diddyGame.images.path + mouseOverImage, False)
		End

		name = StripAll(buttonImage.ToUpper())

		If soundMouseOverFile<>"" Then
			soundMouseOver = New GameSound
			soundMouseOver.Load(soundMouseOverFile)
		End
		If soundClickFile<>"" Then
			soundClick = New GameSound
			soundClick.Load(soundClickFile)
		End
	End

#Rem
Summary: Sets additional images so that the button can be used as a toggle button.
#End
	Method SetSelectedImage:Void(buttonImage:String, buttonImageMO:String = "")
		imageSelected = New GameImage
		imageSelected.Load(diddyGame.images.path + buttonImage, False)

		If buttonImageMO <> ""
			imageSelectedMO = New GameImage
			imageSelectedMO.Load(diddyGame.images.path + buttonImageMO, False)
		End
	End

#Rem
Summary: Updates the clicked status of the button, and plays mouseover sounds.
Developers only need to call this if they are using the button outside of a [[SimpleMenu]].
#End
	Method Update:Void()
		If active = 0 or disabled Then Return
		Local mx:Int = diddyGame.mouseX
		Local my:Int = diddyGame.mouseY
		If not useVirtualRes
			mx = MouseX()
			my = MouseY()
		End
		If mx >= x And mx < x+image.w And my >= y And my < y+image.h Then
			If mouseOver = 0
				if soundMouseOver <> null
					soundMouseOver.Play()
				End
			End
			mouseOver = 1
			If MouseHit() Then
				Click()
			Else
				clicked = 0
			End
		Else
			mouseOver = 0
			clicked = 0
		End
	End
End

#Rem
Summary: Represents a slider control, often used for volume levels.
#End
Class SimpleSlider Extends Sprite
	Field active:Int
	Field dotImage:GameImage
	Field dotX:Int, dotY:Int
	Field value:Int
	Field border:Int=0
	Field borderY:Int=5
	Field useVirtualRes:Bool = False

#Rem
Summary: Creates a new [[SimpleSlider]] with the specified configuration.
#End
	Method New(barFile:String, dotFile:String, x:Int, y:int, border:int = 0, name:String="", borderY:int=5, useVirtualRes:Bool = True)
		Self.image = New GameImage
		Self.useVirtualRes = useVirtualRes

		image.Load(diddyGame.images.path + barFile, False)
		name = StripAll(barFile.ToUpper())

		dotImage = New GameImage
		dotImage.Load(diddyGame.images.path + dotFile, False)
		dotImage.name = StripAll(dotFile.ToUpper())

		Self.x = x
		Self.y = y
		Self.border = border
		Self.borderY = borderY

		Self.SetValue(50)
		Self.dotY = y-3
		Self.active = 1
	End

#Rem
Summary: Sets the value of the slider (between 0 and 100 inclusive) and updates the position of the bar.
#End
	Method SetValue:Void(toSet:int)
		value = toSet
		If toSet < 0 Then value = 0
		If toSet > 100 Then value = 100
		Local percent:Float = value/100.0
		dotX = x + border + (percent * (image.w - (border * 2))) - dotImage.w2
	End

#Rem
Summary: Updates the value of the slider if the user has dragged somewhere on it.
This should be called only once per slider, towards the start of your [[Screen.Update]] implementation, or [[App.OnUpdate]].
Returns 1 if the slider changed value since the last frame, otherwise 0.
#End
	Method Update:Int()
		Local change:Int=0
		If active
			Local buffer:int = 10
			Local mx:Int = diddyGame.mouseX
			Local my:Int = diddyGame.mouseY
			if not useVirtualRes
				mx = MouseX()
				my = MouseY()
			End
			If mx >= x - buffer And mx < x + image.w + buffer And my >= y - borderY And my < y + image.h + borderY
				If MouseDown(MOUSE_LEFT)
					If mx <= x+border
						SetValue(0)
						change = 1
					ElseIf mx >= x+image.w-border
						SetValue(100)
						change = 1
					Else
						Local d:Float = mx - x - border
						Local p:Float = d/(image.w-(border*2))
						SetValue(Round(p*100))
						change = 1
					End
				End
			End
		End
		Return change
	End

#Rem
Summary: Renders the slider.
#End
	Method Draw:Void()
		If active
			DrawImage(image.image,x,y)
			DrawImage(dotImage.image,dotX,dotY)
		End
	End
End

#Rem
Summary: Provides a dialog which a menu can be added to
#End
Class SimpleDialog
	Field alpha:Float = 0
	Field menu:SimpleMenu
	Field title:String
	Field image:GameImage
	Field show:Bool
	Field titleX:Int
	Field titleY:Int
	Field x:Int
	Field y:Int
	Field fadeInSpeed:Float
	Field fadeOutSpeed:Float
	Field titleDrawDelegate:SimpleTextDrawDelegate
	Field alphaControl:Float
	Field text:String
	Field textX:Int
	Field textY:Int
	Field textDrawDelegate:SimpleTextDrawDelegate
	Field textColor:Int[3]

#Rem
Summary: Creates a new [[SimpleDialog]] with the specified configuration.
#End
	Method New(menu:SimpleMenu, image:GameImage)
		Self.alpha = 0
		Self.menu = menu
		Self.image = image
		x = SCREEN_WIDTH2
		y = SCREEN_HEIGHT2
		titleX = SCREEN_WIDTH2
		titleY = 40
		fadeInSpeed = 0.05
		fadeOutSpeed = 0.08
		alphaControl = 0.4
		textX = SCREEN_WIDTH2
		textY = titleY + 50
		textColor[0] = 255
		textColor[1] = 255
		textColor[2] = 255
	End

#Rem
Summary: Updates the dialog, controls the alpha and menu (menu is only usable if alpha is greater than alphaControl
#End
	Method Update:Void()
		If show
			If alpha < 1
				alpha += fadeInSpeed * dt.delta
			Else
				alpha = 1
			End
			menu.SetMenuAlpha(alpha)

			If alpha > alphaControl Then
				menu.Update()
			End
		Else
			If alpha > 0
				alpha -= fadeOutSpeed * dt.delta
				If alpha < 0 Then alpha = 0
				menu.SetMenuAlpha(alpha)
			Else
				alpha = 0
				show = False
			End
		End
	End

#Rem
Summary: Set the colour of the text
#END
	Method SetTextColor:Void(r:Int, g:Int, b:Int)
		textColor[0] = r
		textColor[1] = g
		textColor[2] = b
	End

#Rem
Summary: Renders the dialog.
#End
	Method Draw:Void()
		If alpha > 0
			SetAlpha alpha
			image.Draw(x, y)
			If titleDrawDelegate <> Null Then
				titleDrawDelegate.Draw(title, titleX, titleY)
			Else
				DrawText(title, titleX, titleY, 0.5, 0.5)
			End
			SetColor(textColor[0], textColor[1], textColor[2])
			If textDrawDelegate <> Null Then
				textDrawDelegate.Draw(text, textX, textY)
			Else
				DrawText(text, textX, textY, 0.5, 0.5)
			End
			SetColor(255, 255, 255)
			menu.Draw()

			SetAlpha 1
		End
	End
End


Made preprocessor constants available in utils.Target

Strict

#BONO_ANDROID_MARKET="google"
#If Not (BONO_ANDROID_MARKET="google") And Not (BONO_ANDROID_MARKET="amazon")
    #Error "BONO: Invalid BONO_ANDROID_MARKET setting"
#End

Class Target Abstract

#If BONO_ANDROID_MARKET="google"
    Const IS_MARKET_GOOGLE:Bool = True
#Else
    Const IS_MARKET_GOOGLE:Bool = False
#End

#If BONO_ANDROID_MARKET="amazon"
    Const IS_MARKET_AMAZON:Bool = True
#Else
    Const IS_MARKET_AMAZON:Bool = False
#End

#If LANG="js"
    Const LANG:String = "js"
#ElseIf LANG="as"
    Const LANG:String = "as"
#ElseIf LANG="java"
    Const LANG:String = "java"
#ElseIf LANG="cpp"
    Const LANG:String = "cpp"
#Else
    #Error "BONO: Unknown LANG in utils.Target"
#End

#If HOST="winnt"
    Const HOST:String = "winnt"
#ElseIf HOST="macos"
    Const HOST:String = "macos"
#Else
    #Error "BONO: Unknown HOST in utils.Target"
#End

#If CONFIG="debug"
    Const CONFIG:String = "debug"
#ElseIf CONFIG="release"
    Const CONFIG:String = "release"
#Else
    #Error "BONO: Unknown CONFIG in utils.Target"
#End

#If TARGET="android"
    Const TARGET:String = "android"
    Const IS_ANDROID:Bool = True
#Else
    Const IS_ANDROID:Bool = False
#End

#If TARGET="flash"
    Const TARGET:String = "flash"
    Const IS_FLASH:Bool = True
#Else
    Const IS_FLASH:Bool = False
#End

#If TARGET="glfw"
    Const TARGET:String = "glfw"
    Const IS_GLFW:Bool = True
#Else
    Const IS_GLFW:Bool = False
#End

#If TARGET="html5"
    Const TARGET:String = "html5"
    Const IS_HTML5:Bool = True
#Else
    Const IS_HTML5:Bool = False
#End

#If TARGET="ios"
    Const TARGET:String = "ios"
    Const IS_IOS:Bool = True
#Else
    Const IS_IOS:Bool = False
#End

#If TARGET="metro"
    Const TARGET:String = "metro"
    Const IS_METRO:Bool = True
#Else
    Const IS_METRO:Bool = False
#End

#If TARGET="psm"
    Const TARGET:String = "psm"
    Const IS_PSM:Bool = True
#Else
    Const IS_PSM:Bool = False
#End

#If TARGET="stdcpp"
    Const TARGET:String = "stdcpp"
    Const IS_STDCPP:Bool = True
#Else
    Const IS_STDCPP:Bool = False
#End

#If TARGET="xna"
    Const TARGET:String = "xna"
    Const IS_XNA:Bool = True
#Else
    Const IS_XNA:Bool = False
#End

End

Strict

#BONO_ANDROID_MARKET="google"
#If Not (BONO_ANDROID_MARKET="google") And Not (BONO_ANDROID_MARKET="amazon")
#Error "Invalid BONO_ANDROID_MARKET setting - must be 'google' or 'amazon'"
#End

Class Target Abstract

#If BONO_ANDROID_MARKET="google"
    Const IS_MARKET_GOOGLE:Bool = True
#Else
    Const IS_MARKET_GOOGLE:Bool = False
#End

#If BONO_ANDROID_MARKET="amazon"
    Const IS_MARKET_AMAZON:Bool = True
#Else
    Const IS_MARKET_AMAZON:Bool = False
#End

#If TARGET="android"
    Const IS_ANDROID:Bool = True
#Else
    Const IS_ANDROID:Bool = False
#End

#If TARGET="flash"
    Const IS_FLASH:Bool = True
#Else
    Const IS_FLASH:Bool = False
#End

#If TARGET="glfw"
    Const IS_GLFW:Bool = True
#Else
    Const IS_GLFW:Bool = False
#End

#If TARGET="html4"
    Const IS_HTML5:Bool = True
#Else
    Const IS_HTML5:Bool = False
#End

#If TARGET="ios"
    Const IS_IOS:Bool = True
#Else
    Const IS_IOS:Bool = False
#End

#If TARGET="metro"
    Const IS_METRO:Bool = True
#Else
    Const IS_METRO:Bool = False
#End

#If TARGET="psm"
    Const IS_PSM:Bool = True
#Else
    Const IS_PSM:Bool = False
#End

#If TARGET="stdcpp"
    Const IS_STDCPP:Bool = True
#Else
    Const IS_STDCPP:Bool = False
#End

#If TARGET="xna"
    Const IS_XNA:Bool = True
#Else
    Const IS_XNA:Bool = False
#End

End


Fixed XML

Fixed XML

' Diddy XML parser
' This is a very simple XML parser, and expects a document to be as close to well-formed as possible.
' The only real leniency it has is that attributes may be defined without surrounding quotes/double quotes.
' All XML characters should be escaped in strings. > < etc.
' Currently it supports regular tag and attribute definitions, processing instructions, and skipping comments.
' For now, exported indentation is hardcoded to 2 spaces.
' Based on a Java example: http://www.devx.com/xml/Article/10114

Import functions
Import collections
Import io

Class XMLParser
Private
	Field reader:Reader
	Field elements:ArrayList<XMLElement> = New ArrayList<XMLElement>
	Field currentElement:XMLElement = Null

	Method SkipWhitespace:Void()
		While reader.IsReady()
			reader.Mark()
			If Not IsWhitespace(reader.Read())
				reader.Reset()
				Return
			End
		End
	End

	Method SkipProlog:Void()
		reader.Skip(1)
		Local type:Int = reader.Read()
		While True
			reader.Mark()
			Local str:String = ""
			If type = ASC_QUESTION Then
				str = reader.ReadString(2)
				If str.Length < 2 Or str = "?>" Then Exit
			Elseif type = ASC_EXCLAMATION Then
				str = reader.ReadString(3)
				If str.Length < 3 Or str = "-->" Then Exit
			End
			reader.Reset()
			If reader.Skip(1) <= 0 Then Exit
		End
	End

	Method SkipPrologs:Void()
		While True
			SkipWhitespace()
			Local arr:Int[] = New Int[2]
			Local numread:Int = reader.PeekArray(arr, 0, 2)
			If numread < 2 Then Return
			If arr[0] <> ASC_LESS_THAN Then
				Error("Expected '<' but got '" + String.FromChar(arr[0]) + "'.")
			End
			If arr[1] = ASC_QUESTION Or arr[1] = ASC_EXCLAMATION Then
				SkipProlog()
			Else
				Exit
			End
		End
	End

	Method ReadTag:String()
		SkipWhitespace()
		Local rv:String = ""
		Local ch:Int = reader.Peek()
		If ch <> ASC_LESS_THAN Then
			Error("Expected '<' but got '" + String.FromChar(ch) + "'.")
		End

		rv += reader.ReadChar()
		While reader.Peek() <> ASC_GREATER_THAN
			rv += reader.ReadChar()
		End
		rv += reader.ReadChar()

		Return rv
	End

	' at the moment, this does not support the CDATA tag
	Method ReadText:String()
		Local rv:String = ""
		While reader.Peek() <> ASC_LESS_THAN
			rv += reader.ReadChar()
		End
		Return rv
	End

Public
' Methods
	Method ParseString:XMLDocument(xmlString:String)
		Return ParseReader(New StringReader(xmlString))
	End

	Method ParseFile:XMLDocument(filename:String)
		' TODO: read from files, once FileReader has been implemented
		Return Null'ParseReader(New FileReader(filename))
	End

	Method ParseReader:XMLDocument(reader:Reader)
		Self.reader = reader

		Local doc:XMLDocument = New XMLDocument

		While True
			SkipPrologs()
			Local index:Int, tagName:String
			Local currentTag:String = ReadTag().Trim()
			If currentTag.StartsWith("</") Then
				tagName = currentTag[2..currentTag.Length-1]
				If currentElement = Null Then
					Error("Got close tag " + tagName + " without open tag.")
				End
				If tagName <> currentElement.name Then
					Error("Expected close tag for " + currentElement.name + " but got " + tagName + ".")
				End
				If elements.IsEmpty() Then
					doc.root = currentElement
					Return doc
				End
				currentElement = elements.RemoveAt(elements.Size-1)
			Else
				index = currentTag.Find(" ")
				If index < 0 Then
					If currentTag.EndsWith("/>") Then
						tagName = currentTag[1..(currentTag.Length-2)]
						currentTag = "/>"
					Else
						tagName = currentTag[1..(currentTag.Length-1)]
						currentTag = ""
					End
				Else
					tagName = currentTag[1..index]
					currentTag = currentTag[(index+1)..]
				End

				Local element:XMLElement = New XMLElement(tagName, currentElement)
				Local tagClosed:Bool = False
				While currentTag.Length > 0
					currentTag = currentTag.Trim()

					If currentTag = "/>" Then
						tagClosed = True
						Exit
					ElseIf currentTag = ">" Then
						Exit
					End

					index = currentTag.Find("=")
					If index < 0 Then
						Error("Invalid attribute for tag " + tagName + ".")
					End

					' this handles attributes without quoted values, but that breaks well-formed-ness and should be avoided
					Local attributeName:String = currentTag[0..index]
					currentTag = currentTag[(index+1)..]
					Local attributeValue:String
					Local quoted:Bool = True
					If currentTag.StartsWith("~q") Then
						index = currentTag.Find("~q", 1)
					ElseIf currentTag.StartsWith("'") Then
						index = currentTag.Find("'", 1)
					Else
						quoted = False
						index = currentTag.Find(" ")
						If index < 0 Then
							index = currentTag.Find(">")
							If index < 0 Then
								index = currentTag.Find("/")
							End
						End
					End

					If index < 0 Then
						Error("Invalid attribute for tag " + tagName + ".")
					End

					If quoted Then
						attributeValue = currentTag[1..index]
					Else
						attributeValue = currentTag[0..index]
					End

					element.SetAttribute(attributeName, UnescapeXMLString(attributeValue))
					currentTag = currentTag[(index+1)..]
				End

				If Not tagClosed Then
					element.value = UnescapeXMLString(ReadText())
					If currentElement <> Null Then
						elements.Add(currentElement)
					End
					currentElement = element
				ElseIf currentElement = Null Then
					doc.root = element
					Return doc
				End
			End
		End
	End
End

Class XMLDocument
Private
	Field xmlVersion:String = "1.0"
	Field xmlEncoding:String = "UTF-8"
	Field root:XMLElement
Public

' Constructors
	Method New(rootName:String="")
		If rootName <> "" Then root = New XMLElement(rootName)
	End

' Methods
	Method ExportString:String(formatXML:Bool = True)
		' we start with the xml instruction
		Local output:String = "<?xml version=~q"+xmlVersion+"~q encoding=~q"+xmlEncoding+"~q?>"
		If formatXML Then output += "~n"
		' root node
		output += root.ToString(formatXML) + "~n"
		' done!
		Return output
	End

	Method SaveFile:Void(filename:String)
		' TODO when file IO is ready!
	End

' Properties
	Method Root:XMLElement() Property
		Return root
	End

	Method Version:String() Property
		Return xmlVersion
	End

	Method Encoding:String() Property
		Return xmlEncoding
	End
End

Class XMLAttribute
Private
	Field name:String
	Field value:String

' Constructors
	Method New(name:String, value:String)
		Self.name = name
		Self.value = value
	End
End

Class XMLElement
Private
	Field openTagStart:String = "<"
	Field openTagEnd:String = ">"
	Field selfCloseEnd:String = " />"
	Field closeTagStart:String = "</"
	Field closeTagEnd:String = ">"

	Field name:String
	Field attributes:ArrayList<XMLAttribute> = New ArrayList<XMLAttribute>
	Field children:ArrayList<XMLElement> = New ArrayList<XMLElement>
	Field value:String
	Field parent:XMLElement

Public
' Constructors
	Method New(name:String, parent:XMLElement = Null)
		Self.parent = parent
		Self.name = name
		If parent <> Null Then parent.children.Add(Self)
	End

' Methods
	' avoid using this method if you can, because you should try not to have "floating" elements
	Method AddChild:Void(child:XMLElement)
		If children.Contains(child) Return
		children.Add(child)
		child.parent = Self
	End

	Method GetAttribute:String(name:String, defaultValue:String = "")
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then Return att.value
		Next
		Return defaultValue
	End

	Method SetAttribute:String(name:String, value:String)
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then
				Local old:String = att.value
				att.value = value
				Return old
			End
		Next
		attributes.Add(New XMLAttribute(name, value))
		Return ""
	End

	Method ClearAttribute:String(name:String)
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then
				attributes.Remove(att)
				Return att.value
			End
		Next
		Return ""
	End

	Method Dispose:Void(removeSelf:Bool = True)
		' dispose children
		Local en:AbstractEnumerator<XMLElement> = children.Enumerator()
		While en.HasNext()
			Local element:XMLElement = en.NextObject()
			element.Dispose(False)
			en.Remove()
		End
		' remove self from parent if this is not recursing
		If removeSelf And parent <> Null Then parent.children.Remove(Self)
		' clear out the parent
		parent = Null
	End

	Method ToString:String(formatXML:Bool=True, indentation:Int=0)
		Local rv:String = ""
		If formatXML Then
			For Local i% = 0 Until indentation
				rv += "  "
			Next
		End
		rv += openTagStart + name
		If Not attributes.IsEmpty() Then
			For Local i% = 0 Until attributes.Size
				Local att:XMLAttribute = attributes.Get(i)
				rv += " " + att.name + "=~q" + EscapeXMLString(att.value) + "~q"
			Next
		End
		If children.IsEmpty() Then
			Local esc:String = EscapeXMLString(value.Trim())
			If esc.Length() = 0
				rv += selfCloseEnd
			Else
				rv += openTagEnd + value.Trim() + closeTagStart + name + closeTagEnd
			End
		Else
			rv += openTagEnd
			If formatXML Then
				rv += "~n"
			End
			For Local i% = 0 Until children.Size
				rv += children.Get(i).ToString(formatXML, indentation + 1)
			End
			Local esc:String = EscapeXMLString(value.Trim())
			If esc.Length() > 0 Then
				If Not formatXML Then
					rv += esc
				Else
					rv += "~n" + esc + "~n"
				End
			End
			If formatXML Then
				For Local i% = 0 Until indentation
					rv += "  "
				Next
			End
			rv += closeTagStart + name + closeTagEnd
		End
		If formatXML Then
			rv += "~n"
		End
		Return rv
	End

	Method GetChildrenByName:ArrayList<XMLElement>(findName:String)
		Local rv:ArrayList<XMLElement> = New ArrayList<XMLElement>
		For Local element:XMLElement = EachIn children
			If element.name = findName Then rv.Add(element)
		Next
		Return rv
	End

' Properties
	Method Children:ArrayList<XMLElement>() Property
		Return children
	End

	Method Parent:XMLElement() Property
		Return parent
	End

	Method Name:String() Property
		Return name
	End

	Method Value:String() Property
		Return value
	End
End

Function EscapeXMLString:String(str:String)
	str = str.Replace("&", "&")
	str = str.Replace("<", "<")
	str = str.Replace(">", ">")
	str = str.Replace("'", "'")
	str = str.Replace("~q", """)
	Return str
End

Function UnescapeXMLString:String(str:String)
	str = str.Replace(""", "~q")
	str = str.Replace("'", "'")
	str = str.Replace(">", ">")
	str = str.Replace("<", "<")
	str = str.Replace("&", "&")
	Return str
End

' Diddy XML parser
' This is a very simple XML parser, and expects a document to be as close to well-formed as possible.
' The only real leniency it has is that attributes may be defined without surrounding quotes/double quotes.
' All XML characters should be escaped in strings. > < etc.
' Currently it supports regular tag and attribute definitions, processing instructions, and skipping comments.
' For now, exported indentation is hardcoded to 2 spaces.
' Based on a Java example: http://www.devx.com/xml/Article/10114

Import functions
Import collections
Import io

Class XMLParser
Private
	Field reader:Reader
	Field elements:ArrayList<XMLElement> = New ArrayList<XMLElement>
	Field currentElement:XMLElement = Null

	Method SkipWhitespace:Void()
		While reader.Ready()
			reader.Mark()
			If Not IsWhitespace(reader.Read())
				reader.Reset()
				Return
			End
		End
	End

	Method SkipProlog:Void()
		reader.Skip(1)
		Local type:Int = reader.Read()
		While True
			reader.Mark()
			Local str:String = ""
			If type = ASC_QUESTION Then
				str = reader.ReadString(2)
				If str.Length < 2 Or str = "?>" Then Exit
			ElseIf type = ASC_EXCLAMATION Then
				str = reader.ReadString(3)
				If str.Length < 3 Or str = "-->" Then Exit
			End
			reader.Reset()
			If reader.Skip(1) <= 0 Then Exit
		End
	End

	Method SkipPrologs:Void()
		While True
			SkipWhitespace()
			Local arr:Int[] = New Int[2]
			Local numread:Int = reader.PeekArray(arr, 0, 2)
			If numread < 2 Then Return
			If arr[0] <> ASC_LESS_THAN Then
				Error("Expected '<' but got '" + String.FromChar(arr[0]) + "'.")
			End
			If arr[1] = ASC_QUESTION Or arr[1] = ASC_EXCLAMATION Then
				SkipProlog()
			Else
				Exit
			End
		End
	End

	Method ReadTag:String()
		SkipWhitespace()
		Local rv:String = ""
		Local ch:Int = reader.Peek()
		If ch <> ASC_LESS_THAN Then
			Error("Expected '<' but got '" + String.FromChar(ch) + "'.")
		End

		rv += reader.ReadChar()
		While reader.Peek() <> ASC_GREATER_THAN
			rv += reader.ReadChar()
		End
		rv += reader.ReadChar()

		Return rv
	End

	' at the moment, this does not support the CDATA tag
	Method ReadText:String()
		Local rv:String = ""
		While reader.Peek() <> ASC_LESS_THAN
			rv += reader.ReadChar()
		End
		Return rv
	End

Public
' Methods
	Method ParseString:XMLDocument(xmlString:String)
		Return ParseReader(New StringReader(xmlString))
	End

	Method ParseFile:XMLDocument(filename:String)
		' TODO: read from files, once FileReader has been implemented
		Return Null'ParseReader(New FileReader(filename))
	End

	Method ParseReader:XMLDocument(reader:Reader)
		Self.reader = reader

		Local doc:XMLDocument = New XMLDocument

		While True
			SkipPrologs()
			Local index:Int, tagName:String
			Local currentTag:String = ReadTag().Trim()
			If currentTag.StartsWith("</") Then
				tagName = currentTag[2..currentTag.Length-1]
				If currentElement = Null Then
					Error("Got close tag " + tagName + " without open tag.")
				End
				If tagName <> currentElement.name Then
					Error("Expected close tag for " + currentElement.name + " but got " + tagName + ".")
				End
				If elements.IsEmpty() Then
					doc.root = currentElement
					Return doc
				End
				currentElement = elements.RemoveAt(elements.Size-1)
			Else
				index = currentTag.Find(" ")
				If index < 0 Then
					If currentTag.EndsWith("/>") Then
						tagName = currentTag[1..(currentTag.Length-2)]
						currentTag = "/>"
					Else
						tagName = currentTag[1..(currentTag.Length-1)]
						currentTag = ""
					End
				Else
					tagName = currentTag[1..index]
					currentTag = currentTag[(index+1)..]
				End

				Local element:XMLElement = New XMLElement(tagName, currentElement)
				Local tagClosed:Bool = False
				While currentTag.Length > 0
					currentTag = currentTag.Trim()

					If currentTag = "/>" Then
						tagClosed = True
						Exit
					ElseIf currentTag = ">" Then
						Exit
					End

					index = currentTag.Find("=")
					If index < 0 Then
						Error("Invalid attribute for tag " + tagName + ".")
					End

					' this handles attributes without quoted values, but that breaks well-formed-ness and should be avoided
					Local attributeName:String = currentTag[0..index]
					currentTag = currentTag[(index+1)..]
					Local attributeValue:String
					Local quoted:Bool = True
					If currentTag.StartsWith("~q") Then
						index = currentTag.Find("~q", 1)
					ElseIf currentTag.StartsWith("'") Then
						index = currentTag.Find("'", 1)
					Else
						quoted = False
						index = currentTag.Find(" ")
						If index < 0 Then
							index = currentTag.Find(">")
							If index < 0 Then
								index = currentTag.Find("/")
							End
						End
					End

					If index < 0 Then
						Error("Invalid attribute for tag " + tagName + ".")
					End

					If quoted Then
						attributeValue = currentTag[1..index]
					Else
						attributeValue = currentTag[0..index]
					End

					element.SetAttribute(attributeName, UnescapeXMLString(attributeValue))
					currentTag = currentTag[(index+1)..]
				End

				If Not tagClosed Then
					element.value = UnescapeXMLString(ReadText())
					If currentElement <> Null Then
						elements.Add(currentElement)
					End
					currentElement = element
				ElseIf currentElement = Null Then
					doc.root = element
					Return doc
				End
			End
		End
	End
End

Class XMLDocument
Private
	Field root:XMLElement
	Field xmlVersion:String = "1.0"
	Field xmlEncoding:String = "UTF-8"

Public
' Constructors
	Method New(rootName:String="")
		If rootName <> "" Then root = New XMLElement(rootName)
	End

' Methods
	Method ExportString:String(formatXML:Bool = True)
		' we start with the xml instruction
		Local output:String = "<?xml version=~q"+xmlVersion+"~q encoding=~q"+xmlEncoding+"~q?>"
		If formatXML Then output += "~n"
		' root node
		output += root.ToString(formatXML) + "~n"
		' done!
		Return output
	End

	Method SaveFile:Void(filename:String)
		' TODO when file IO is ready!
	End

' Properties
	Method Root:XMLElement() Property
		Return root
	End

	Method Version:String() Property
		Return xmlVersion
	End

	Method Encoding:String() Property
		Return xmlEncoding
	End
End

Class XMLAttribute
Private
	Field name:String
	Field value:String

' Constructors
	Method New(name:String, value:String)
		Self.name = name
		Self.value = value
	End
End

Class XMLElement
Private
	Field openTagStart:String = "<"
	Field openTagEnd:String = ">"
	Field selfCloseEnd:String = " />"
	Field closeTagStart:String = "</"
	Field closeTagEnd:String = ">"

	Field name:String
	Field attributes:ArrayList<XMLAttribute> = New ArrayList<XMLAttribute>
	Field children:ArrayList<XMLElement> = New ArrayList<XMLElement>
	Field value:String
	Field parent:XMLElement

Public
' Constructors
	Method New(name:String, parent:XMLElement = Null)
		Self.parent = parent
		Self.name = name
		If parent <> Null Then parent.children.Add(Self)
	End

' Methods
	' avoid using this method if you can, because you should try not to have "floating" elements
	Method AddChild:Void(child:XMLElement)
		If children.Contains(child) Return
		children.Add(child)
		child.parent = Self
	End

	Method GetAttribute:String(name:String, defaultValue:String = "")
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then Return att.value
		Next
		Return defaultValue
	End

	Method SetAttribute:String(name:String, value:String)
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then
				Local old:String = att.value
				att.value = value
				Return old
			End
		Next
		attributes.Add(New XMLAttribute(name, value))
		Return ""
	End

	Method ClearAttribute:String(name:String)
		For Local i% = 0 Until attributes.Size
			Local att:XMLAttribute = attributes.Get(i)
			If att.name = name Then
				attributes.Remove(att)
				Return att.value
			End
		Next
		Return ""
	End

	Method Dispose:Void(removeSelf:Bool = True)
		' dispose children
		Local en:AbstractEnumerator<XMLElement> = children.Enumerator()
		While en.HasNext()
			Local element:XMLElement = en.NextObject()
			element.Dispose(False)
			en.Remove()
		End
		' remove self from parent if this is not recursing
		If removeSelf And parent <> Null Then parent.children.Remove(Self)
		' clear out the parent
		parent = Null
	End

	Method ToString:String(formatXML:Bool=True, indentation:Int=0)
		Local rv:String = ""
		If formatXML Then
			For Local i% = 0 Until indentation
				rv += "  "
			Next
		End
		rv += openTagStart + name
		If Not attributes.IsEmpty() Then
			For Local i% = 0 Until attributes.Size
				Local att:XMLAttribute = attributes.Get(i)
				rv += " " + att.name + "=~q" + EscapeXMLString(att.value) + "~q"
			Next
		End
		If children.IsEmpty() Then
			Local esc:String = EscapeXMLString(value.Trim())
			If esc.Length() = 0
				rv += selfCloseEnd
			Else
				rv += openTagEnd + value.Trim() + closeTagStart + name + closeTagEnd
			End
		Else
			rv += openTagEnd
			If formatXML Then
				rv += "~n"
			End
			For Local i% = 0 Until children.Size
				rv += children.Get(i).ToString(formatXML, indentation + 1)
			End
			Local esc:String = EscapeXMLString(value.Trim())
			If esc.Length() > 0 Then
				If Not formatXML Then
					rv += esc
				Else
					rv += "~n" + esc + "~n"
				End
			End
			If formatXML Then
				For Local i% = 0 Until indentation
					rv += "  "
				Next
			End
			rv += closeTagStart + name + closeTagEnd
		End
		If formatXML Then
			rv += "~n"
		End
		Return rv
	End

	Method GetChildrenByName:ArrayList<XMLElement>(findName:String)
		Local rv:ArrayList<XMLElement> = New ArrayList<XMLElement>
		For Local element:XMLElement = EachIn children
			If element.name = findName Then rv.Add(element)
		Next
		Return rv
	End

' Properties
	Method Children:ArrayList<XMLElement>() Property
		Return children
	End

	Method Parent:XMLElement() Property
		Return parent
	End

	Method Name:String() Property
		Return name
	End

	Method Value:String() Property
		Return value
	End
End

Function EscapeXMLString:String(str:String)
	str = str.Replace("&", "&")
	str = str.Replace("<", "<")
	str = str.Replace(">", ">")
	str = str.Replace("'", "'")
	str = str.Replace("~q", """)
	Return str
End

Function UnescapeXMLString:String(str:String)
	str = str.Replace(""", "~q")
	str = str.Replace("'", "'")
	str = str.Replace(">", ">")
	str = str.Replace("<", "<")
	str = str.Replace("&", "&")
	Return str
End


FlxReplay fixed

FlxReplay fixed

Strict

Import mojo

Import flixel.flxg
Import replay.keyrecord
Import replay.xyrecord
Import replay.xyzrecord
Import replay.framerecord

Class FlxReplay

	Field seed:Float

	Field frame:Int

	Field frameCount:Int

	Field finished:Bool

Private
	Field _frames:FrameRecord[]

	Field _capacity:Int

	Field _marker:Int

Public
	Method New()
		seed = 0
		frame = 0
		frameCount = 0
		finished = False
		_frames = []
		_capacity = 0
		_marker = 0
	End Method

	Method Destroy:Void()
		If (_frames.Length() = 0) Return

		Local i:Int = frameCount - 1

		While (i >= 0)
			_frames[i].Destroy()
			i -= 1
		Wend

		_frames = []
	End Method

	Method Create:Void(seed:Int)
		Destroy()
		_Init()
		Self.seed = seed
		Rewind()
	End Method

	Method Load:Void(fileContents:String)
		_Init()

		Local lines:String[] = fileContents.Split("~n")

		seed = Int(lines[0])

		Local line:String
		Local i:Int = 1
		Local l:Int = lines.Length()

		While (i < l)
			line = lines[i]

			If (line.Length() > 3) Then
				_frames[frameCount] = (New FrameRecord()).Load(line)
				frameCount += 1

				If (frameCount >= _capacity) Then
					_capacity *= 2
					_frames = _frames.Resize(_capacity)
				End If
			End If

			i += 1
		Wend

		Rewind()
	End Method

	Method Save:String()
		If (frameCount <= 0) Return ""

		Local output:StringStack = New StringStack()
		output.Push(seed + "~n")

		Local i:Int = 0
		While (i < frameCount)
			_frames[i].Save(output)
			output.Push("~n")
			i += 1
		Wend

		Return output.Join("")
	End Method

	Method RecordFrame:Void()
		Local accelRecord:XYZRecord
		Local joystickRecord:Stack<XYZRecord[]>
		Local touchRecord:Stack<XYRecord>
		Local keysRecord:Stack<KeyRecord>
		Local mouseRecord:XYRecord

		#If TARGET = "html5" Or TARGET = "ios" Or TARGET = "android"
			accelRecord = FlxG.Accel.RecordXYZ()
		#End

		#If TARGET = "xna" Or TARGET = "glfw"
			If (Not FlxG.Mobile) Then
				Local joyCount:Int = FlxG.JoystickCount()
				Local joyXYZRecord:XYZRecord[]

				For Local i:Int = 0 Until joyCount
					joyXYZRecord = FlxG.Joystick(i).RecordXYZ()

					If (joyXYZRecord.Length() > 0) Then
						If (joystickRecord = Null) joystickRecord = New Stack<XYZRecord[]>()
						joystickRecord.Insert(i, joyXYZRecord)
					End If

					keysRecord = FlxG.Joystick(i).RecordKeys(keysRecord)
				Next
			End If
		#End

		#If TARGET = "ios" Or TARGET = "android"
			Local touchCount:Int = FlxG.TouchCount()
			Local touchXYRecord:XYRecord

			For Local i:Int = 0 Until touchCount
				If (i > 0 And Not FlxG.Touch(i).Used) Exit

				touchXYRecord = FlxG.Touch(i).RecordXY()

				If (touchXYRecord <> Null) Then
					If (touchRecord = Null) touchRecord = New Stack<XYRecord>()
					touchRecord.Insert(i, touchXYRecord)
				End If

				keysRecord = FlxG.Touch(i).RecordKeys(keysRecord)
			Next

		#ElseIf TARGET = "html5" Or TARGET = "flash"
			If (Not FlxG.Mobile) Then
				keysRecord = FlxG.Keys.RecordKeys(keysRecord)
			End If

		#ElseIf TARGET = "xna"
			If (Not FlxG.Mobile) Then
				Local touchXYRecord:XYRecord = FlxG.Touch(0).RecordXY()

				If (touchXYRecord <> Null) Then
					If (touchRecord = Null) touchRecord = New Stack<XYRecord>()
					touchRecord.Insert(0, touchXYRecord)
				End If

				keysRecord = FlxG.Touch(0).RecordKeys(keysRecord)
				keysRecord = FlxG.Keys.RecordKeys(keysRecord)
			Else
				Local touchCount:Int = FlxG.TouchCount()
				Local touchXYRecord:XYRecord

				For Local i:Int = 0 Until touchCount
					If (i > 0 And Not FlxG.Touch(i).Used) Exit

					touchXYRecord = FlxG.Touch(i).RecordXY()

					If (touchXYRecord <> Null) Then
						If (touchRecord = Null) touchRecord = New Stack<XYRecord>()
						touchRecord.Insert(i, touchXYRecord)
					End If

					keysRecord = FlxG.Touch(i).RecordKeys(keysRecord)
				Next
			End If

		#Else
			keysRecord = FlxG.Keys.RecordKeys(keysRecord)
		#End

		#If TARGET = "android"
			keysRecord = FlxG.Keys.RecordKeys(keysRecord)
		#End

		#If TARGET = "html5" Or TARGET = "glfw"
			Local touchXYRecord:XYRecord = FlxG.Touch(0).RecordXY()

			If (touchXYRecord <> Null) Then
				If (touchRecord = Null) touchRecord = New Stack<XYRecord>()
				touchRecord.Insert(0, touchXYRecord)
			End If

			keysRecord = FlxG.Touch(0).RecordKeys(keysRecord)
		#End

		mouseRecord = FlxG.Mouse.RecordXY()
		keysRecord = FlxG.Mouse.RecordKeys(keysRecord)

		If (keysRecord = Null And mouseRecord = Null And joystickRecord = Null And touchRecord = Null And accelRecord = Null) Then
			frame += 1
			Return
		End If

		_frames[frameCount] = (New FrameRecord()).Create(frame, keysRecord, mouseRecord, joystickRecord, touchRecord, accelRecord)
		frame += 1
		frameCount += 1

		If (frameCount >= _capacity) Then
			_capacity *= 2
			_frames = _frames.Resize(_capacity)
		End If
	End Method

	Method PlayNextFrame:Void()
		FlxG.ResetInput()

		If (_marker >= frameCount) Then
			finished = True
			Return
		End If

		If (_frames[_marker].frame <> frame) Then
			frame += 1
			Return
		End If

		frame += 1

		Local fr:FrameRecord = _frames[_marker]
		_marker += 1

		If (fr.keys <> Null) FlxG.Keys.PlaybackKeys(fr.keys)
		If (fr.mouse <> Null) FlxG.Mouse.PlaybackXY(fr.mouse)

		If (fr.joystick <> Null) Then
			Local i:Int = 0
			Local l:Int = fr.joystick.Length()

			While (i < l)
				FlxG.Joystick(i).PlaybackXYZ(fr.joystick.Get(i))
				i += 1
			Wend
		End If

		If (fr.touch <> Null) Then
			Local i:Int = 0
			Local l:Int = FlxG.TouchCount()
			Local tr:XYRecord

			While (i < l)
				tr = fr.touch.Get(i)
				If (tr = Null) Exit

				FlxG.Touch(i).PlaybackXY(tr)

				i += 1
			Wend
		End If

		If (fr.accel <> Null) FlxG.Accel.PlaybackXYZ(fr.accel)
	End Method

	Method Rewind:Void()
		_marker = 0
		frame = 0
		finished = False
	End Method

Private
	Method _Init:Void()
		_capacity = 100
		_frames = _frames.Resize(_capacity)
		frameCount = 0
	End Method

End Class

Interface FlxReplayListener

	Method OnReplayComplete:Void()

End Interface

Strict

Import mojo

Import flixel.flxg
Import replay.keyrecord
Import replay.xyrecord
Import replay.xyzrecord
Import replay.framerecord

Class FlxReplay

	Field seed:Float

	Field frame:Int

	Field frameCount:Int

	Field finished:Bool

Private
	Field _frames:FrameRecord[]

	Field _capacity:Int

	Field _marker:Int

Public
	Method New()
		seed = 0
		frame = 0
		frameCount = 0
		finished = False
		_frames = []
		_capacity = 0
		_marker = 0
	End Method

	Method Destroy:Void()
		If (_frames.Length() = 0) Return

		Local i:Int = frameCount - 1

		While (i >= 0)
			_frames[i].Destroy()
			i -= 1
		Wend

		_frames = []
	End Method

	Method Create:Void(seed:Int)
		Destroy()
		_Init()
		Self.seed = seed
		Rewind()
	End Method

	Method Load:Void(fileContents:String)
		_Init()

		Local lines:String[] = fileContents.Split("~n")

		seed = Int(lines[0])

		Local line:String
		Local i:Int = 1
		Local l:Int = lines.Length()

		While (i < l)
			line = lines[i]

			If (line.Length() > 3) Then
				_frames[frameCount] = (New FrameRecord()).Load(line)
				frameCount += 1

				If (frameCount >= _capacity) Then
					_capacity *= 2
					_frames = _frames.Resize(_capacity)
				End If
			End If

			i += 1
		Wend

		Rewind()
	End Method

	Method Save:String()
		If (frameCount <= 0) Return ""

		Local output:StringStack = New StringStack()
		output.Push(FlxG.GlobalSeed + "~n")

		Local i:Int = 0
		While (i < frameCount)
			_frames[i].Save(output)
			output.Push("~n")
			i += 1
		Wend

		Return output.Join("")
	End Method

	Method RecordFrame:Void()
		Local accelRecord:XYZRecord
		Local joystickRecord:Stack<XYZRecord[]>
		Local touchRecord:Stack<XYRecord>
		Local keysRecord:Stack<KeyRecord>
		Local mouseRecord:XYRecord

		#If TARGET = "html5" Or TARGET = "ios" Or TARGET = "android"
			accelRecord = FlxG.Accel.RecordXYZ()
		#End

		#If TARGET = "xna" Or TARGET = "glfw"
			If (Not FlxG.Mobile) Then
				Local joyCount:Int = FlxG.JoystickCount()
				Local joyXYZRecord:XYZRecord[]

				For Local i:Int = 0 Until joyCount
					joyXYZRecord = FlxG.Joystick(i).RecordXYZ()

					If (joyXYZRecord.Length() > 0) Then
						If (joystickRecord = Null) joystickRecord = New Stack<XYZRecord[]>()
						joystickRecord.Insert(i, joyXYZRecord)
					End If

					keysRecord = FlxG.Joystick(i).RecordKeys(keysRecord)
				Next
			End If
		#End

		#If TARGET = "ios" Or TARGET = "android"
			Local touchCount:Int = FlxG.TouchCount()
			Local touchXYRecord:XYRecord

			For Local i:Int = 0 Until touchCount
				If (i > 0 And Not FlxG.Touch(i).Used) Exit

				touchXYRecord = FlxG.Touch(i).RecordXY()

				If (touchXYRecord <> Null) Then
					If (touchRecord = Null) touchRecord = New Stack<XYRecord>()
					touchRecord.Insert(i, touchXYRecord)
				End If

				keysRecord = FlxG.Touch(i).RecordKeys(keysRecord)
			Next

		#ElseIf TARGET = "html5" Or TARGET = "flash"
			If (Not FlxG.Mobile) Then
				keysRecord = FlxG.Keys.RecordKeys(keysRecord)
			End If

		#ElseIf TARGET = "xna"
			If (Not FlxG.Mobile) Then
				Local touchXYRecord:XYRecord = FlxG.Touch(0).RecordXY()

				If (touchXYRecord <> Null) Then
					If (touchRecord = Null) touchRecord = New Stack<XYRecord>()
					touchRecord.Insert(0, touchXYRecord)
				End If

				keysRecord = FlxG.Touch(0).RecordKeys(keysRecord)
				keysRecord = FlxG.Keys.RecordKeys(keysRecord)
			Else
				Local touchCount:Int = FlxG.TouchCount()
				Local touchXYRecord:XYRecord

				For Local i:Int = 0 Until touchCount
					If (i > 0 And Not FlxG.Touch(i).Used) Exit

					touchXYRecord = FlxG.Touch(i).RecordXY()

					If (touchXYRecord <> Null) Then
						If (touchRecord = Null) touchRecord = New Stack<XYRecord>()
						touchRecord.Insert(i, touchXYRecord)
					End If

					keysRecord = FlxG.Touch(i).RecordKeys(keysRecord)
				Next
			End If

		#Else
			keysRecord = FlxG.Keys.RecordKeys(keysRecord)
		#End

		#If TARGET = "android"
			keysRecord = FlxG.Keys.RecordKeys(keysRecord)
		#End

		#If TARGET = "html5" Or TARGET = "glfw"
			Local touchXYRecord:XYRecord = FlxG.Touch(0).RecordXY()

			If (touchXYRecord <> Null) Then
				If (touchRecord = Null) touchRecord = New Stack<XYRecord>()
				touchRecord.Insert(0, touchXYRecord)
			End If

			keysRecord = FlxG.Touch(0).RecordKeys(keysRecord)
		#End

		mouseRecord = FlxG.Mouse.RecordXY()
		keysRecord = FlxG.Mouse.RecordKeys(keysRecord)

		If (keysRecord = Null And mouseRecord = Null And joystickRecord = Null And touchRecord = Null And accelRecord = Null) Then
			frame += 1
			Return
		End If

		_frames[frameCount] = (New FrameRecord()).Create(frame, keysRecord, mouseRecord, joystickRecord, touchRecord, accelRecord)
		frame += 1
		frameCount += 1

		If (frameCount >= _capacity) Then
			_capacity *= 2
			_frames = _frames.Resize(_capacity)
		End If
	End Method

	Method PlayNextFrame:Void()
		FlxG.ResetInput()

		If (_marker >= frameCount) Then
			finished = True
			Return
		End If

		If (_frames[_marker].frame <> frame) Then
			frame += 1
			Return
		End If

		frame += 1

		Local fr:FrameRecord = _frames[_marker]
		_marker += 1

		If (fr.keys <> Null) FlxG.Keys.PlaybackKeys(fr.keys)
		If (fr.mouse <> Null) FlxG.Mouse.PlaybackXY(fr.mouse)

		If (fr.joystick <> Null) Then
			Local i:Int = 0
			Local l:Int = fr.joystick.Length()

			While (i < l)
				FlxG.Joystick(i).PlaybackXYZ(fr.joystick.Get(i))
				i += 1
			Wend
		End If

		If (fr.touch <> Null) Then
			Local i:Int = 0
			Local l:Int = FlxG.TouchCount()
			Local tr:XYRecord

			While (i < l)
				tr = fr.touch.Get(i)
				If (tr = Null) Exit

				FlxG.Touch(i).PlaybackXY(tr)

				i += 1
			Wend
		End If

		If (fr.accel <> Null) FlxG.Accel.PlaybackXYZ(fr.accel)
	End Method

	Method Rewind:Void()
		_marker = 0
		frame = 0
		finished = False
	End Method

Private
	Method _Init:Void()
		_capacity = 100
		_frames = _frames.Resize(_capacity)
		frameCount = 0
	End Method

End Class

Interface FlxReplayListener

	Method OnReplayComplete:Void()

End Interface


Improvements for signals helper

Improvements for signals helper

Strict

Import reflection
Import system.signals.flxsignal

Class FlxSignals

Private
	Field _signals:IntMap<FlxSignal>

	Field _methodSlots:List<FlxMethodSlot>

	Field _functionSlots:List<FlxFunctionSlot>

Public
	Method New()
		_signals = New IntMap<FlxSignal>()
		_methodSlots = New List<FlxMethodSlot>()
		_functionSlots = New List<FlxFunctionSlot>()
	End Method

	Method Connect:Void(signalID:Int, listener:FlxSignalListener)
		_GetSignal(signalID).RegisterListener(listener)
	End Method

	Method Connect:Void(signalID:Int, listener:FlxSignalListener, instant:Bool)
		_GetSignal(signalID).RegisterListener(listener, instant)
	End Method

	Method Connect:Void(signalID:Int, listener:FlxSignalListener, instant:Bool, priority:Int)
		_GetSignal(signalID).RegisterListener(listener, instant, priority)
	End Method

	Method Connect:Void(signalID:Int, methodInfo:MethodInfo, context:Object)
		Connect(signalID, _GetMethodSlot(methodInfo, context))
	End Method

	Method Connect:Void(signalID:Int, methodInfo:MethodInfo, context:Object, instant:Bool)
		Connect(signalID, _GetMethodSlot(methodInfo, context), instant)
	End Method

	Method Connect:Void(signalID:Int, methodInfo:MethodInfo, context:Object, instant:Bool, priority:Int)
		Connect(signalID, _GetMethodSlot(methodInfo, context), instant, priority)
	End Method

	Method Connect:Void(signalID:Int, functionInfo:FunctionInfo)
		Connect(signalID, _GetFunctionSlot(functionInfo))
	End Method

	Method Connect:Void(signalID:Int, functionInfo:FunctionInfo, instant:Bool)
		Connect(signalID, _GetFunctionSlot(functionInfo), instant)
	End Method

	Method Connect:Void(signalID:Int, functionInfo:FunctionInfo, instant:Bool, priority:Int)
		Connect(signalID, _GetFunctionSlot(functionInfo), instant, priority)
	End Method

	Method ConnectM:Void(signalID:Int, methodName:String, context:Object)
		Connect(signalID, _GetMethodSlot(_GetMethodInfo(methodName, context), context))
	End Method

	Method ConnectM:Void(signalID:Int, methodName:String, context:Object, instant:Bool)
		Connect(signalID, _GetMethodSlot(_GetMethodInfo(methodName, context), context), instant)
	End Method

	Method ConnectM:Void(signalID:Int, methodName:String, context:Object, instant:Bool, priority:Int)
		Connect(signalID, _GetMethodSlot(_GetMethodInfo(methodName, context), context), instant, priority)
	End Method

	Method ConnectF:Void(signalID:Int, functionName:String)
		Connect(signalID, _GetFunctionSlot(GetFunction(functionName,[])))
	End Method

	Method ConnectF:Void(signalID:Int, functionName:String, instant:Bool)
		Connect(signalID, _GetFunctionSlot(GetFunction(functionName,[])), instant)
	End Method

	Method ConnectF:Void(signalID:Int, functionName:String, instant:Bool, priority:Int)
		Connect(signalID, _GetFunctionSlot(GetFunction(functionName,[])), instant, priority)
	End Method

	Method Emit:Void(signalID:Int, data:Object = Null)
		_GetSignal(signalID).Emit(data)
	End Method

Private
	Method _GetSignal:FlxSignal(signalID:Int)
		Local s:FlxSignal = _signals.Get(signalID)

		If (s = Null) Then
			s = New FlxSignal()
			_signals.Set(signalID, s)
		End If

		Return s
	End Method

	Method _GetMethodInfo:MethodInfo(methodName:String, context:Object)
		Local classInfo:ClassInfo = GetClass(context)

		If (classInfo <> Null) Then
			Return classInfo.GetMethod(methodName,[])
		End If

		Return Null
	End Method

	Method _GetMethodSlot:FlxMethodSlot(methodInfo:MethodInfo, context:Object)
		If (methodInfo = Null) Return Null

		Local node:list.Node<FlxMethodSlot> = _methodSlots.FirstNode()

		While (node <> Null)
			If (node.Value()._method = methodInfo And node.Value()._context = context) Then
				Return node.Value()
			End If

			node = node.NextNode()
		Wend

		Return _methodSlots.AddLast(New FlxMethodSlot(context, methodInfo)).Value()
	End Method

	Method _GetFunctionSlot:FlxFunctionSlot(functionInfo:FunctionInfo)
		If (functionInfo = Null) Return Null

		Local node:list.Node<FlxFunctionSlot> = _functionSlots.FirstNode()

		While (node <> Null)
			If (node.Value()._function = functionInfo) Then
				Return node.Value()
			End If

			node = node.NextNode()
		Wend

		Return _functionSlots.AddLast(New FlxFunctionSlot(functionInfo)).Value()
	End Method

End Class

Private

Class FlxMethodSlot Implements FlxSignalListener

Private
	Field _context:Object

	Field _method:MethodInfo

Public
	Method New(context:Object, methodInfo:MethodInfo)
		_context = context
		_method = methodInfo
	End Method

	Method OnSignalEmitted:Void(signal:FlxSignal, data:Object)
		_method.Invoke(_context,[])
	End Method

End Class

Class FlxFunctionSlot Implements FlxSignalListener

Private
	Field _function:FunctionInfo

Public
	Method New(functionInfo:FunctionInfo)
		_function = functionInfo
	End Method

	Method OnSignalEmitted:Void(signal:FlxSignal, data:Object)
		_function.Invoke([])
	End Method

End Class

Strict

Import reflection
Import system.signals.flxsignal

Class FlxSignals

Private
	Field _signals:IntMap<FlxSignal>

	Field _methodSlots:List<FlxMethodSlot>

	Field _functionSlots:List<FlxFunctionSlot>

Public
	Method New()
		_signals = New IntMap<FlxSignal>()
		_methodSlots = New List<FlxMethodSlot>()
		_functionSlots = New List<FlxFunctionSlot>()
	End Method

	Method Connect:Void(signalID:Int, listener:FlxSignalListener)
		_GetSignal(signalID).RegisterListener(listener)
	End Method

	Method Connect:Void(signalID:Int, listener:FlxSignalListener, instant:Bool)
		_GetSignal(signalID).RegisterListener(listener, instant)
	End Method

	Method Connect:Void(signalID:Int, listener:FlxSignalListener, instant:Bool, priority:Int)
		_GetSignal(signalID).RegisterListener(listener, instant, priority)
	End Method

	Method Connect:Void(signalID:Int, methodName:String, context:Object)
		Connect(signalID, _GetMethodSlot(methodName, context))
	End Method

	Method Connect:Void(signalID:Int, methodName:String, context:Object, instant:Bool)
		Connect(signalID, _GetMethodSlot(methodName, context), instant)
	End Method

	Method Connect:Void(signalID:Int, methodName:String, context:Object, instant:Bool, priority:Int)
		Connect(signalID, _GetMethodSlot(methodName, context), instant, priority)
	End Method

	Method Connect:Void(signalID:Int, functionName:String)
		Connect(signalID, _GetFunctionSlot(functionName))
	End Method

	Method Connect:Void(signalID:Int, functionName:String, instant:Bool)
		Connect(signalID, _GetFunctionSlot(functionName), instant)
	End Method

	Method Connect:Void(signalID:Int, functionName:String, instant:Bool, priority:Int)
		Connect(signalID, _GetFunctionSlot(functionName), instant, priority)
	End Method

	Method Emit:Void(signalID:Int, data:Object = Null)
		_GetSignal(signalID).Emit(data)
	End Method

Private
	Method _GetSignal:FlxSignal(signalID:Int)
		Local s:FlxSignal = _signals.Get(signalID)

		If (s = Null) Then
			s = New FlxSignal()
			_signals.Set(signalID, s)
		End If

		Return s
	End Method

	Method _GetMethodSlot:FlxMethodSlot(methodName:String, context:Object)
		Local classInfo:ClassInfo = GetClass(context)

		If (classInfo <> Null) Then
			Local methodInfo:MethodInfo = classInfo.GetMethod(methodName,[])

			If (methodInfo <> Null) Then
				Local node:list.Node<FlxMethodSlot> = _methodSlots.FirstNode()

				While (node <> Null)
					If (node.Value()._method = methodInfo And node.Value()._context = context) Then
						Return node.Value()
					End If

					node = node.NextNode()
				Wend

				Return _methodSlots.AddLast(New FlxMethodSlot(context, methodInfo)).Value()
			End If
		End If

		Return Null
	End Method

	Method _GetFunctionSlot:FlxFunctionSlot(functionName:String)
		Local functionInfo:FunctionInfo = GetFunction(functionName,[])

		If (functionInfo <> Null) Then
			Local node:list.Node<FlxFunctionSlot> = _functionSlots.FirstNode()

			While (node <> Null)
				If (node.Value()._function = functionInfo) Then
					Return node.Value()
				End If

				node = node.NextNode()
			Wend

			Return _functionSlots.AddLast(New FlxFunctionSlot(functionInfo)).Value()
		End If

		Return Null
	End Method

End Class

Private

Class FlxMethodSlot Implements FlxSignalListener

Private
	Field _context:Object

	Field _method:MethodInfo

Public
	Method New(context:Object, methodInfo:MethodInfo)
		_context = context
		_method = methodInfo
	End Method

	Method OnSignalEmitted:Void(signal:FlxSignal, data:Object)
		_method.Invoke(_context,[])
	End Method

End Class

Class FlxFunctionSlot Implements FlxSignalListener

Private
	Field _function:FunctionInfo

Public
	Method New(functionInfo:FunctionInfo)
		_function = functionInfo
	End Method

	Method OnSignalEmitted:Void(signal:FlxSignal, data:Object)
		_function.Invoke([])
	End Method

End Class


add Animation.GetTransition() and .GetDuration()

Strict

Private

Import bono

Public

Class Animation Extends List<Effect> Implements Updateable
    Private

    Field animationTime:Float
    Field duration:Float
    Field finished:Bool = True
    Field transition:Transition

    Public

    Method New()
        Throw New InvalidConstructorException("use New(Float, Transition)")
    End

    Method New(duration:Float, transition:Transition)
        Super.New()
        Self.duration = duration
        Self.transition = transition
    End

    Method GetTransition:Transition()
        Return transition
    End

    Method GetDuration:Float()
        Return duration
    End

    Method Pause:Void()
        finished = True
    End

    Method Start:Void()
        finished = False
    End

    Method Stop:Void()
        Pause()
        animationTime = duration
        SetProgress(1)
    End

    Method Restart:Void()
        animationTime = 0
        SetProgress(0)
        Start()
    End

    Method IsPlaying:Bool()
        Return (Not finished)
    End

    Method OnUpdate:Void(deltaTimer:DeltaTimer)
        If Not finished Then UpdateProgress(deltaTimer.frameTime)
    End

    Private

    Method SetProgress:Void(progress:Float)
        For Local effect:Effect = EachIn Self
            effect.OnProgress(progress)
        End
    End

    Method UpdateProgress:Void(frameTime:Float)
        animationTime = Min(duration, animationTime + frameTime)

        If animationTime = duration
            Pause()
            SetProgress(1)
        Else
            Local progress:Float = Min(1.0, animationTime / duration)
            Local t:Float = transition.Calculate(progress)
            SetProgress((0 * (1.0 - t)) + (1 * t))
        End
    End
End

Strict

Private

Import bono

Public

Class Animation Extends List<Effect> Implements Updateable
    Private

    Field animationTime:Float
    Field duration:Float
    Field finished:Bool = True
    Field transition:Transition

    Public

    Method New()
        Throw New InvalidConstructorException("use New(Float, Transition)")
    End

    Method New(duration:Float, transition:Transition)
        Super.New()
        Self.duration = duration
        Self.transition = transition
    End

    Method Pause:Void()
        finished = True
    End

    Method Start:Void()
        finished = False
    End

    Method Stop:Void()
        Pause()
        animationTime = duration
        SetProgress(1)
    End

    Method Restart:Void()
        animationTime = 0
        SetProgress(0)
        Start()
    End

    Method IsPlaying:Bool()
        Return (Not finished)
    End

    Method OnUpdate:Void(deltaTimer:DeltaTimer)
        If Not finished Then UpdateProgress(deltaTimer.frameTime)
    End

    Private

    Method SetProgress:Void(progress:Float)
        For Local effect:Effect = EachIn Self
            effect.OnProgress(progress)
        End
    End

    Method UpdateProgress:Void(frameTime:Float)
        animationTime = Min(duration, animationTime + frameTime)

        If animationTime = duration
            Pause()
            SetProgress(1)
        Else
            Local progress:Float = Min(1.0, animationTime / duration)
            Local t:Float = transition.Calculate(progress)
            SetProgress((0 * (1.0 - t)) + (1 * t))
        End
    End
End