FlxText refactoring complete

FlxText refactoring complete

Strict

Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_8_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_8_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_9_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_9_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_10_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_10_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_11_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_11_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_12_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_12_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_13_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_13_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_14_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_14_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_15_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_15_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_16_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_16_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_17_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_17_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_18_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_18_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_19_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_19_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_20_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_20_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_21_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_21_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_22_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_22_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_23_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_23_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_24_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_24_flx.txt"

Import flxextern
Import flxsprite
Import flxcamera
Import flxg
Import system.flxcolor
Import system.flxassetsmanager

#If FLX_TEXT_DRIVER = "angelfont"
	Import vendor.angelfont
#End

Class FlxText Extends FlxSprite Implements FlxSpriteRenderer

	Global ClassObject:Object

	Const ALIGN_LEFT:Float = 0
	Const ALIGN_CENTER:Float = 0.5
	Const ALIGN_RIGHT:Float = 1

	Const SYSTEM_FONT:String = "system"

Private
	Global _FontLoader:FlxFontLoader = New FlxFontLoader()

	Global _FontsManager:FlxFontsManager = New FlxFontsManager()

	Field _shadow:FlxColor

	Field _value:String

	Field _lines:FlxTextLine[]

	Field _countLines:Int

	Field _alignment:Float

	Field _fontFamily:String

	Field _fontSize:Int

	Field _fontHeight:Int

#If FLX_TEXT_DRIVER = "angelfont"
	Field _fontObject:AngelFont
#End

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

		SetRenderer(Self)
		_shadow = New FlxColor(0)

		Self.width = width
		frameWidth = Self.width
		moves = False

		SetFormat(SYSTEM_FONT)
		Text = text
	End Method

	Method Destroy:Void()
		Local i:Int = 0, l:Int = _lines.Length()

		While (i < l)
			_lines[i] = Null
			i += 1
		Wend

		_fontObject = Null
		_shadow = Null

		Super.Destroy()
	End Method

	Method SetFormat:FlxText(font:String = "", size:Int = 0, color:Int = FlxG.WHITE, alignment:Float = ALIGN_LEFT, shadowColor:Int = 0)
		_alignment = alignment
		_fontFamily = font
		_fontSize = FlxAssetsManager.GetFont(font).GetValidSize(size)

		Self.Color = color
		Shadow = shadowColor

		_ResetFont()
		_ResetHelpers()

		Return Self
	End Method

	Method SetWidth:Void(width:Float)
		Self.width = width
		frameWidth = Self.width
		_ResetHelpers()
	End Method

	Method Text:String() Property
		Return _value
	End Method

	Method Text:Void(text:String) Property
		_value = text
		_ResetHelpers()
	End Method

	Method Size:Void(size:Int) Property
		_fontSize = FlxAssetsManager.GetFont(_fontFamily).GetValidSize(size)
		_ResetFont()
		_ResetHelpers()
	End Method

	Method Size:Int() Property
		Return _fontSize
	End Method

	Method Font:String() Property
		Return _fontFamily
	End Method

	Method Font:Void(font:String) Property
		_fontFamily = font
		_ResetFont()
		_ResetHelpers()
	End Method

	Method Alignment:Float() Property
		Return _alignment
	End Method

	Method Alignment:Void(alignment:Float) Property
		_alignment = alignment
		_ResetAlignment()
	End Method

	Method Shadow:Int() Property
		Return _shadow.argb
	End Method

	Method Shadow:Void(color:Int) Property
		_shadow.SetARGB(color)
	End Method

	Method OnSpriteRender:Void(x:Float, y:Float)
		If (_shadow.argb <> 0) Then
			Local oldColor:Int = FlxG._LastDrawingColor
			Local oldAlpha:Int = FlxG._LastDrawingAlpha

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

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

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

				If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
					SetAlpha(_mixedAlpha)
				End If
			Else
				If (FlxG._LastDrawingAlpha <> _shadow.a) Then
					SetAlpha(_shadow.a)
				End If
			End If

			_fontObject.DrawText(Self, x + 1, y + 1)

			_mixedColor.SetRGB(oldColor)
			SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
			SetAlpha(oldAlpha)
		End If

		_fontObject.DrawText(Self, x, y)
	End Method

Private
	Method _ResetHelpers:Void()
		If (_value.Length() > 0) Then
			If (width <> 0) Then
				_ParseText()
			Else
				_countLines = 1
			End If

			Self.height = _GetHeight()
			frameHeight = Self.height
		Else
			Self.height = 0
			frameHeight = 0
		End If

		Super._ResetHelpers()
	End Method

	Method _ResetFont:Void()
		_FontLoader.fontFamily = _fontFamily
		_FontLoader.fontSize = _fontSize

		_fontObject = _FontsManager.GetResource(_fontFamily + _fontSize, _FontLoader)
		_fontHeight = _fontObject.GetTextHeight(Self)
	End Method

	Method _ResetAlignment:Void()
		If (_value.Length() = 0) Return

		For Local line:Int = 0 Until _countLines
			_lines[line].x = (width - _lines[line].width) * _alignment
		Next
	End Method

	Method _GetHeight:Int()
		If (width = 0) Then
			Return _fontHeight
		Else
			Return _fontHeight + _fontObject.lineGap * (_countLines - 1)
		End If
	End Method

	Method _ParseText:Void()
		_countLines = 0

		Local prevOffset:Int = 0
		Local offsetN:Int = _value.Find("~n", 0)
		Local offsetR:Int = _value.Find("~r", 0)
		Local offset:Int = 0

		If (offsetN >= 0 And offsetR >= 0) Then
			offset = Min(offsetN, offsetR)
		Else
			offset = Max(offsetN, offsetR)
		End if

		If (offset >= 0) Then
			While (offset >= 0)
				_BuildLines(prevOffset, offset)

				prevOffset = offset + 1

				offsetN = _value.Find("~n", prevOffset)
				offsetR = _value.Find("~r", prevOffset)

				If (offsetN >= 0 And offsetR >= 0) Then
					offset = Min(offsetN, offsetR)
				Else
					offset = Max(offsetN, offsetR)
				End if
			Wend

			_BuildLines(prevOffset, _value.Length())
		Else
			_BuildLines(0, _value.Length())
		End If

		_ResetAlignment()
	End Method

	Method _BuildLines:Void(startPos:Int, endPos:Int)
		Local textWidth:Int = _fontObject.GetTextWidth(Self, startPos, endPos)

		If (width < textWidth) Then
			Local textLength:Int = startPos - endPos

			Local range:Int = Ceil(textLength / Float(Floor(textWidth / Float(width)) + 1))
			Repeat
				range += 1
				textWidth = _fontObject.GetTextWidth(Self, 0, range)
			Until (textWidth >= width)

			Local maxOffset:Int = range
			Local minOffset:Int = 0
			Local offset:Int = maxOffset
			Local tmpOffset:Int = 0
			Local dirty:Bool = False
			Local finalTextWidth:Int = 0

			Repeat
				Repeat
					offset -= 1
					If (offset - minOffset <= 1) Then
						offset = minOffset + 1
						Exit
					End if

					tmpOffset = -1
					For Local i:Int = offset - 1 To minOffset Step - 1
						If (_value[i] = KEY_SPACE) tmpOffset = i
					Next

					If (tmpOffset < 0) Then
						tmpOffset = _GetMinOffset(minOffset, offset)
					Else
						If (offset - minOffset > 1 And _value[offset] = KEY_SPACE) Then
							minOffset += 1
							offset +=  Min(offset + 2, maxOffset)
							Continue
						EndIf
					End If

					offset = tmpOffset + minOffset
					textWidth = _fontObject.GetTextWidth(Self, minOffset, offset)
				Until (textWidth <= width)

				dirty = False
				finalTextWidth = _fontObject.GetTextWidth(Self, minOffset)

				If (finalTextWidth > width And textLength - minOffset > 1) Then
					For Local i:Int = minOffset Until offset
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							minOffset += 1
							dirty = True
						Else
							Exit
						End If
					Next

					For Local i:Int = offset - 1 To minOffset Step - 1
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							offset -= 1
							dirty = True
						Else
							Exit
						End If
					Next

					If (offset - minOffset = 0) Continue

					If ( Not dirty) Then
						_AddLine(minOffset, offset, textWidth)
					Else
						_AddLine(minOffset, offset, _fontObject.GetTextWidth(Self, minOffset, offset))
					End If

					minOffset = offset
					maxOffset = minOffset + range
					offset = maxOffset
				Else
					Local l:Int = _value.Length()

					For Local i:Int = minOffset Until l
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							minOffset += 1
							dirty = True
						Else
							Exit
						End If
					Next

					For Local i:Int = l - 1 To minOffset Step - 1
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							l -= 1
							dirty = True
						Else
							Exit
						End If
					Next

					If (offset - l = 0) Continue

					If ( Not dirty) Then
						_AddLine(minOffset, l, finalTextWidth)
					Else
						_AddLine(minOffset, offset, _fontObject.GetTextWidth(Self, minOffset, l))
					End If

					Exit
				End If
			Forever
		Else
			_AddLine(startPos, endPos, textWidth)
		End If
	End Method

	Method _GetMinOffset:Int(startPos:Int, endPos:Int)
		Local offset:Int = startPos - endPos

		While (_fontObject.GetTextWidth(Self, 0, offset) > width)
			offset -= 1
			If (offset = 0) Return offset
		Wend

		Return offset
	End Method

	Method _AddLine:FlxTextLine(startPos:Int, endPos:Int, width:Int)
		Local line:FlxTextLine

		If (_countLines = _lines.Length()) Then
			_lines = _lines.Resize(_countLines * 2 + 10)
			line = New FlxTextLine(startPos, endPos, width)
			_lines[_countLines] = line
		Else
			line = _lines[_countLines]
			If (line = Null) Then
				line = New FlxTextLine(startPos, endPos, width)
				_lines[_countLines] = line
			Else
				line.Reset(startPos, endPos, width)
			End If
		End If

		_countLines += 1
		Return line
	End Method

End Class

Private
Class FlxTextLine

	Field x:Float

	Field y:Float

	Field startPos:Int

	Field endPos:Int

	Field width:Int

	Method New(startPos:Int, endPos:Int, width:Int)
		Reset(startPos, endPos, width)
	End Method

	Method Reset:Void(startPos:Int, endPos:Int, width:Int)
		Self.startPos = startPos
		Self.endPos = endPos
	End Method

End Class

#If FLX_TEXT_DRIVER = "angelfont"
	Class FlxFontLoader Extends FlxResourceLoader<AngelFont>

		Field fontFamily:String = FlxText.SYSTEM_FONT
		Field fontSize:Int

		Method Load:AngelFont(name:String)
			Local font:AngelFont = New AngelFont()
			font.LoadFontXml(FlxAssetsManager.GetFont(fontFamily).GetPath(fontSize))

			Return font
		End Method

	End Class

	Class FlxFontsManager Extends FlxResourcesManager<AngelFont>
	End Class

	Class AngelFont
	Private
		Field image:Image[] = New Image[1]
		Field chars:Char[256]

		Field kernPairs:IntMap<IntMap<KernPair>> = New IntMap<IntMap<KernPair>>
		Global firstKp:IntMap<KernPair>
		Global secondKp:KernPair

		Field iniText:String

		Field xOffset:Int
		Field yOffset:Int

	Public
		Field useKerning:Bool = True

		Field height:Int = 0
		Field heightOffset:Int = 9999

		Field lineGap:Int = 5

		Method LoadFontXml:Void(url:String)
			iniText = LoadString(url+".fnt")
			Local lines:String[] = iniText.Split(String.FromChar(10))
			Local firstLine:String = lines[0]
			If firstLine.Contains("<?xml")
				Local lineList:List<String> = New List<String>(lines)
				lineList.RemoveFirst()
				lines = lineList.ToArray()
				iniText = "~n".Join(lines)
			End

			Local pageCount:Int = 0

			Local config:= LoadConfig(iniText)

			Local nodes := config.FindNodesByPath("font/chars/char")
			For Local node:= EachIn nodes
				Local id:Int = Int(node.GetAttribute("id"))
				Local page:Int = Int(node.GetAttribute("page"))
				If pageCount < page pageCount = page
				chars[id] = New Char(Int(node.GetAttribute("x")), Int(node.GetAttribute("y")), Int(node.GetAttribute("width")), Int(node.GetAttribute("height")),  Int(node.GetAttribute("xoffset")),  Int(node.GetAttribute("yoffset")),  Int(node.GetAttribute("xadvance")), page)
				Local ch := chars[id]
				If ch.height > Self.height Self.height = ch.height
				If ch.yOffset < Self.heightOffset Self.heightOffset = ch.yOffset
			Next

			nodes = config.FindNodesByPath("font/kernings/kerning")
			For Local node:= EachIn nodes
				Local first:Int = Int(node.GetAttribute("first"))
				firstKp = kernPairs.Get(first)
				If firstKp = Null
					kernPairs.Add(first, New IntMap<KernPair>)
					firstKp = kernPairs.Get(first)
				End

				Local second:Int = Int(node.GetAttribute("second"))
				firstKp.Add(second, New KernPair(first, second, Int(node.GetAttribute("amount"))))
			End

			If pageCount = 0
				image[0] = LoadImage(url+".png")
				If image[0] = Null image[0] = LoadImage(url+"_0.png")
			Else
				For Local page:= 0 To pageCount
					If image.Length < page+1 image = image.Resize(page+1)
					image[page] = LoadImage(url+"_"+page+".png")
				End
			End
		End

		Method GetTextWidth:Int(txt:FlxText, startPos:Int = 0, endPos:Int = -1)
			Local prevChar:Int = 0
			Local width:Int = 0

			If (endPos < 0) endPos = txt._value.Length()

			For Local i:= startPos Until endPos
				Local asc:Int = txt._value[i]
				Local ac:Char = chars[asc]
				Local thisChar:Int = asc
				If ac  <> Null
					If useKerning
						Local firstKp:= kernPairs.Get(prevChar)
						If firstKp <> Null
							Local secondKp:= firstKp.Get(thisChar)
							If secondKp <> Null
								xOffset += secondKp.amount
							End
						Endif
					Endif
					width += ac.xAdvance
					prevChar = thisChar
				Endif
			Next

			Return width
		End Method

		Method GetTextHeight:Int(txt:FlxText, startPos:Int = 0, endPos:Int = -1)
			Local h:Int = 0

			If (endPos < 0) endPos = txt._value.Length()

			For Local i:= startPos Until endPos
				Local asc:Int = txt._value[i]
				Local ac:Char = chars[asc]
				If ac.height+ac.yOffset > h h = ac.height+ac.yOffset
			Next
			Return h
		End

		Method DrawText:Void(txt:FlxText, x:Int, y:Int)
			If (txt._countLines = 1) Then
				Local prevChar:Int = 0
				xOffset = 0

				Local i:Int = 0, l:Int = txt._value.Length
				Local asc:Int, ac:Char, thisChar:Int

				While (i < l)
					If ac <> Null
						If useKerning
							firstKp = kernPairs.Get(prevChar)
							If firstKp <> Null
								secondKp = firstKp.Get(thisChar)
								If secondKp <> Null
									xOffset += secondKp.amount
								End
							Endif
						Endif
						ac.Draw(image[ac.page], x + xOffset, y)
						xOffset += ac.xAdvance
						prevChar = thisChar
					Endif

					i += 1
				Wend
			Else
				Local line:Int, countLines:Int = txt._countLines

				Local prevChar:Int = 0, xOffset:Int = 0, yOffset:Int = 0

				Local i:Int = 0, l:Int = txt._value.Length
				Local asc:Int, ac:Char, thisChar:Int

				While (line < countLines)
					While (i < l)
						If ac <> Null
							If useKerning
								firstKp = kernPairs.Get(prevChar)
								If firstKp <> Null
									secondKp = firstKp.Get(thisChar)
									If secondKp <> Null
										xOffset += secondKp.amount
									End
								Endif
							Endif
							ac.Draw(image[ac.page], x + xOffset, y + yOffset)
							xOffset += ac.xAdvance
							prevChar = thisChar
						Endif

						i += 1
					Wend

					yOffset += txt._fontHeight + lineGap
					line += 1
				Wend
			End If
		End Method

	End Class
#End

Strict

Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_8_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_8_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_9_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_9_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_10_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_10_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_11_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_11_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_12_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_12_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_13_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_13_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_14_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_14_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_15_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_15_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_16_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_16_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_17_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_17_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_18_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_18_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_19_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_19_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_20_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_20_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_21_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_21_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_22_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_22_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_23_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_23_flx.txt"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_24_flx.png"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_24_flx.txt"

Import flxextern
Import flxsprite
Import flxcamera
Import flxg
Import system.flxcolor
Import system.flxassetsmanager

#If FLX_TEXT_DRIVER = "angelfont"
	Import vendor.angelfont
#End

Class FlxText Extends FlxSprite Implements FlxSpriteRenderer

	Global ClassObject:Object

	Const ALIGN_LEFT:Float = 0
	Const ALIGN_CENTER:Float = 0.5
	Const ALIGN_RIGHT:Float = 1

	Const SYSTEM_FONT:String = "system"

Private
	Field _shadow:FlxColor

	Field _textObject:FlxTextInternalObject

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

		_textObject = New FlxTextInternalObject()

		SetRenderer(Self)
		_shadow = New FlxColor(0)

		Self.width = width
		frameWidth = Self.width
		moves = False

		SetFormat(SYSTEM_FONT)
		Text = text
	End Method

	Method Destroy:Void()
		_textObject.Destroy()
		_textObject = Null
		_shadow = Null

		Super.Destroy()
	End Method

	Method SetFormat:FlxText(font:String = "", size:Int = 0, color:Int = FlxG.WHITE, alignment:Float = ALIGN_LEFT, shadowColor:Int = 0)
		_textObject.SetFormat(font, FlxAssetsManager.GetFont(font).GetValidSize(size), alignment)
		Self.Color = color
		Shadow = shadowColor

		Self.height = _textObject.GetHeight()
		frameHeight = Self.height
		_ResetHelpers()

		Return Self
	End Method

	Method SetWidth:Void(width:Float)
		Self.width = width
		frameWidth = Self.width

		_textObject.SetWidth(width)
		_ResetHelpers()
	End Method

	Method Text:String() Property
		Return _textObject.value
	End Method

	Method Text:Void(text:String) Property
		_textObject.SetText(text)

		height = _textObject.GetHeight()
		frameHeight = height
		_ResetHelpers()
	End Method

	Method Size:Void(size:Int) Property
		_textObject.SetFontSize(FlxAssetsManager.GetFont(_textObject._fontFamily).GetValidSize(size))

		height = _textObject.GetHeight()
		frameHeight = height
		_ResetHelpers()
	End Method

	Method Size:Int() Property
		Return _textObject._fontSize
	End Method

	Method Font:String() Property
		Return _textObject._fontFamily
	End Method

	Method Font:Void(font:String) Property
		_textObject.SetFontFamily(font)

		height = _textObject.GetHeight()
		frameHeight = height
		_ResetHelpers()
	End Method

	Method Alignment:Float() Property
		Return _textObject._alignment
	End Method

	Method Alignment:Void(alignment:Float) Property
		_textObject.SetAlignment(alignment)
	End Method

	Method Shadow:Int() Property
		Return _shadow.argb
	End Method

	Method Shadow:Void(color:Int) Property
		_shadow.SetARGB(color)
	End Method

#If FLX_TEXT_DRIVER = "angelfont"
	Method GetFontObject:AngelFont()
		Return _textObject._font
	End Method
#End

	Method OnSpriteRender:Void(x:Float, y:Float)
		If (_shadow.argb <> 0) Then
			Local oldColor:Int = FlxG._LastDrawingColor
			Local oldAlpha:Int = FlxG._LastDrawingAlpha

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

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

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

				If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
					SetAlpha(_mixedAlpha)
				End If
			Else
				If (FlxG._LastDrawingAlpha <> _shadow.a) Then
					SetAlpha(_shadow.a)
				End If
			End If

			_textObject._font._FlxDrawText(_textObject, x + 1, y + 1)

			_mixedColor.SetRGB(oldColor)
			SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
			SetAlpha(oldAlpha)
		End If

		_textObject._font._FlxDrawText(_textObject, x, y)
	End Method

End Class

Class FlxTextInternalObject

	Field value:String

	Field lines:FlxTextInternalLine[]

	Field countLines:Int

Private
	Global _FontLoader:FlxFontLoader = New FlxFontLoader()

	Global _FontsManager:FlxFontsManager = New FlxFontsManager()

	Field _width:Int

	Field _alignment:Float

	Field _fontFamily:String

	Field _fontSize:Int

#If FLX_TEXT_DRIVER = "angelfont"
	Field _font:AngelFont
#End

	Method Destroy:Void()
		Local i:Int = 0, l:Int = lines.Length()

		While (i < l)
			lines[i] = Null
		Wend
	End Method

	Method SetWidth:Void(width:Int)
		_width = width
		If (value.Length() > 0) _ParseText()
	End Method

	Method SetText:Void(text:String)
		value = text
		If (value.Length() > 0) _ParseText()
	End Method

	Method SetFormat:Void(fontFamily:String, fontSize:Int, alignment:Float)
		_alignment = alignment
		_fontFamily = fontFamily
		_fontSize = fontSize

		_ResetFont()
		If (value.Length() > 0) _ParseText()
	End Method

	Method SetFontFamily:Void(fontFamily:String)
		_fontFamily = fontFamily

		_ResetFont()
		If (value.Length() > 0) _ParseText()
	End Method

	Method SetFontSize:Void(fontSize:Int) Property
		_fontSize = fontSize

		_ResetFont()
		If (value.Length() > 0) _ParseText()
	End Method

	Method SetAlignment:Void(alignment:Float) Property
		_alignment = alignment
		If (value.Length() > 0) _ResetAlignment(alignment)
	End Method

	Method GetHeight:Int()
		Local h:Int = 0

		For Local line:Int = 0 Until countLines
			h += _font._FlxGetTextHeight(Self, lines[line].startPos, lines[line].endPos) * 1.5
		Next

		Return h
	End Method

Private
	Method _ResetFont:Void()
		_FontLoader.fontFamily = _fontFamily
		_FontLoader.fontSize = _fontSize

		_font = _FontsManager.GetResource(_fontFamily + _fontSize, _FontLoader)
	End Method

	Method _ResetAlignment:Void(alignment:Float) Property
		_alignment = alignment

		If (value.Length() = 0) Return

		For Local line:Int = 0 Until countLines
			lines[line].x = (_width - lines[line].width) * alignment
		Next
	End Method

	Method _ParseText:Void()
		Local prevOffset:Int = 0
		Local offsetN:Int = value.Find("~n", 0)
		Local offsetR:Int = value.Find("~r", 0)
		Local offset:Int = 0

		If (offsetN >= 0 And offsetR >= 0) Then
			offset = Min(offsetN, offsetR)
		Else
			offset = Max(offsetN, offsetR)
		End if

		If (offset >= 0) Then
			While (offset >= 0)
				_BuildLines(prevOffset, offset)

				prevOffset = offset + 1

				offsetN = value.Find("~n", prevOffset)
				offsetR = value.Find("~r", prevOffset)

				If (offsetN >= 0 And offsetR >= 0) Then
					offset = Min(offsetN, offsetR)
				Else
					offset = Max(offsetN, offsetR)
				End if
			Wend

			_BuildLines(prevOffset, value.Length())
		Else
			_BuildLines(0, value.Length())
		End If

		_ResetAlignment(_alignment)
	End Method

	Method _BuildLines:Void(startPos:Int, endPos:Int)
		Local textWidth:Int = _font._FlxGetTextWidth(Self, startPos, endPos)

		If (_width < textWidth) Then
			Local textLength:Int = startPos - endPos

			Local range:Int = Ceil(textLength / Float(Floor(textWidth / Float(_width)) + 1))
			Repeat
				range += 1
				textWidth = _font._FlxGetTextWidth(Self, 0, range)
			Until (textWidth >= _width)

			Local maxOffset:Int = range
			Local minOffset:Int = 0
			Local offset:Int = maxOffset
			Local tmpOffset:Int = 0
			Local dirty:Bool = False
			Local finalTextWidth:Int = 0

			Repeat
				Repeat
					offset -= 1
					If (offset - minOffset <= 1) Then
						offset = minOffset + 1
						Exit
					End if

					tmpOffset = -1
					For Local i:Int = offset - 1 To minOffset Step - 1
						If (value[i] = KEY_SPACE) tmpOffset = i
					Next

					If (tmpOffset < 0) Then
						tmpOffset = _GetMinOffset(minOffset, offset)
					Else
						If (offset - minOffset > 1 And value[offset] = KEY_SPACE) Then
							minOffset += 1
							offset +=  Min(offset + 2, maxOffset)
							Continue
						EndIf
					End If

					offset = tmpOffset + minOffset
					textWidth = _font._FlxGetTextWidth(Self, minOffset, offset)
				Until (textWidth <= _width)

				dirty = False
				finalTextWidth = _font._FlxGetTextWidth(Self, minOffset)

				If (finalTextWidth > _width And textLength - minOffset > 1) Then
					For Local i:Int = minOffset Until offset
						If (value[i] = KEY_SPACE Or value[i] = KEY_TAB) Then
							minOffset += 1
							dirty = True
						Else
							Exit
						End If
					Next

					For Local i:Int = offset - 1 To minOffset Step - 1
						If (value[i] = KEY_SPACE Or value[i] = KEY_TAB) Then
							offset -= 1
							dirty = True
						Else
							Exit
						End If
					Next

					If (offset - minOffset = 0) Continue

					If ( Not dirty) Then
						_AddLine(minOffset, offset, textWidth)
					Else
						_AddLine(minOffset, offset, _font._FlxGetTextWidth(Self, minOffset, offset))
					End If

					minOffset = offset
					maxOffset = minOffset + range
					offset = maxOffset
				Else
					Local l:Int = value.Length()

					For Local i:Int = minOffset Until l
						If (value[i] = KEY_SPACE Or value[i] = KEY_TAB) Then
							minOffset += 1
							dirty = True
						Else
							Exit
						End If
					Next

					For Local i:Int = l - 1 To minOffset Step - 1
						If (value[i] = KEY_SPACE Or value[i] = KEY_TAB) Then
							l -= 1
							dirty = True
						Else
							Exit
						End If
					Next

					If (offset - l = 0) Continue

					If ( Not dirty) Then
						_AddLine(minOffset, l, finalTextWidth)
					Else
						_AddLine(minOffset, offset, _font._FlxGetTextWidth(Self, minOffset, l))
					End If

					Exit
				End If
			Forever
		Else
			_AddLine(startPos, endPos, textWidth)
		End If
	End Method

	Method _GetMinOffset:Int(startPos:Int, endPos:Int)
		Local offset:Int = startPos - endPos

		While (_font._FlxGetTextWidth(Self, 0, offset) > _width)
			offset -= 1
			If (offset = 0) Return offset
		Wend

		Return offset
	End Method

	Method _AddLine:FlxTextInternalLine(startPos:Int, endPos:Int, width:Int)
		Local line:FlxTextInternalLine

		Print value[startPos..endPos]

		If (countLines = lines.Length()) Then
			lines = lines.Resize(countLines * 2 + 10)
			line = New FlxTextInternalLine(startPos, endPos, width)
			lines[countLines] = line
		Else
			line = lines[countLines]
			If (line = Null) Then
				line = New FlxTextInternalLine(startPos, endPos, width)
				lines[countLines] = line
			Else
				line.Reset(startPos, endPos, width)
			End If
		End If

		countLines += 1
		Return line
	End Method

End Class

Class FlxTextInternalLine

	Field x:Float

	Field y:Float

	Field startPos:Int

	Field endPos:Int

	Field width:Int

	Method New(startPos:Int, endPos:Int, width:Int)
		Reset(startPos, endPos, width)
	End Method

	Method Reset:Void(startPos:Int, endPos:Int, width:Int)
		Self.startPos = startPos
		Self.endPos = endPos
	End Method

End Class

Private
#If FLX_TEXT_DRIVER = "angelfont"
	Class FlxFontLoader Extends FlxResourceLoader<AngelFont>

		Field fontFamily:String = FlxText.SYSTEM_FONT
		Field fontSize:Int

		Method Load:AngelFont(name:String)
			Local font:AngelFont = New AngelFont()
			font.LoadFontXml(FlxAssetsManager.GetFont(fontFamily).GetPath(fontSize))
		End Method

	End Class

	Class FlxFontsManager Extends FlxResourcesManager<AngelFont>
	End Class

	Class AngelFont
	Private
		Field image:Image[] = New Image[1]
		Field chars:Char[256]

		Field kernPairs:IntMap<IntMap<KernPair>> = New IntMap<IntMap<KernPair>>
		Global firstKp:IntMap<KernPair>
		Global secondKp:KernPair

		Field iniText:String

		Field xOffset:Int
		Field yOffset:Int

	Public
		Field useKerning:Bool = True

		Field height:Int = 0
		Field heightOffset:Int = 9999

		Method LoadFontXml:Void(url:String)
			iniText = LoadString(url+".fnt")
			Local lines:String[] = iniText.Split(String.FromChar(10))
			Local firstLine:String = lines[0]
			If firstLine.Contains("<?xml")
				Local lineList:List<String> = New List<String>(lines)
				lineList.RemoveFirst()
				lines = lineList.ToArray()
				iniText = "~n".Join(lines)
			End

			Local pageCount:Int = 0

			Local config:= LoadConfig(iniText)

			Local nodes := config.FindNodesByPath("font/chars/char")
			For Local node:= EachIn nodes
				Local id:Int = Int(node.GetAttribute("id"))
				Local page:Int = Int(node.GetAttribute("page"))
				If pageCount < page pageCount = page
				chars[id] = New Char(Int(node.GetAttribute("x")), Int(node.GetAttribute("y")), Int(node.GetAttribute("width")), Int(node.GetAttribute("height")),  Int(node.GetAttribute("xoffset")),  Int(node.GetAttribute("yoffset")),  Int(node.GetAttribute("xadvance")), page)
				Local ch := chars[id]
				If ch.height > Self.height Self.height = ch.height
				If ch.yOffset < Self.heightOffset Self.heightOffset = ch.yOffset
			Next

			nodes = config.FindNodesByPath("font/kernings/kerning")
			For Local node:= EachIn nodes
				Local first:Int = Int(node.GetAttribute("first"))
				firstKp = kernPairs.Get(first)
				If firstKp = Null
					kernPairs.Add(first, New IntMap<KernPair>)
					firstKp = kernPairs.Get(first)
				End

				Local second:Int = Int(node.GetAttribute("second"))
				firstKp.Add(second, New KernPair(first, second, Int(node.GetAttribute("amount"))))
			End

			If pageCount = 0
				image[0] = LoadImage(url+".png")
				If image[0] = Null image[0] = LoadImage(url+"_0.png")
			Else
				For Local page:= 0 To pageCount
					If image.Length < page+1 image = image.Resize(page+1)
					image[page] = LoadImage(url+"_"+page+".png")
				End
			End
		End

		Method GetTextWidth:Int(txt:FlxTextInternalObject, startPos:Int = 0, endPos:Int = -1)
			Local prevChar:Int = 0
			Local width:Int = 0

			If (endPos < 0) endPos = txt.value.Length()

			For Local i:= startPos Until endPos
				Local asc:Int = txt.value[i]
				Local ac:Char = chars[asc]
				Local thisChar:Int = asc
				If ac  <> Null
					If useKerning
						Local firstKp:= kernPairs.Get(prevChar)
						If firstKp <> Null
							Local secondKp:= firstKp.Get(thisChar)
							If secondKp <> Null
								xOffset += secondKp.amount
							End
						Endif
					Endif
					width += ac.xAdvance
					prevChar = thisChar
				Endif
			Next

			Return width
		End Method

		Method GetTextHeight:Int(txt:FlxTextInternalObject, startPos:Int = 0, endPos:Int = -1)
			Local h:Int = 0

			If (endPos < 0) endPos = txt.value.Length()

			For Local i:= startPos Until endPos
				Local asc:Int = txt.value[i]
				Local ac:Char = chars[asc]
				If ac.height+ac.yOffset > h h = ac.height+ac.yOffset
			Next
			Return h
		End

		Method DrawText:Void(txt:FlxTextInternalObject, x:Int, y:Int)
			Local prevChar:Int = 0
			xOffset = 0
			yOffset = 0
			height = 0

			For Local line:Int = 0 Until txt.countLines
				height = 0

				For Local i:= txt.lines[line].startPos Until txt.lines[line].endPos
					Local asc:Int = txt.value[i]
					Local ac:Char = chars[asc]
					Local thisChar:Int = asc
					If ac  <> Null
						If useKerning
							firstKp = kernPairs.Get(prevChar)
							If firstKp <> Null
								secondKp = firstKp.Get(thisChar)
								If secondKp <> Null
									xOffset += secondKp.amount
								End
							Endif
						Endif
						ac.Draw(image[ac.page], x + xOffset, y + yOffset)
						xOffset += ac.xAdvance
						If (height < ac.yOffset + ac.height) height = ac.yOffset + ac.height
						prevChar = thisChar
					Endif
				Next

				yOffset += height
			Next
		End Method

	End Class
#End


Layer property removed from BaseObject

Layer property removed from BaseObject

Strict

Private

Import partial
Import positionable
Import sizeable
Import vector2d

Public

Class BaseObject Extends Partial Implements Positionable, Sizeable Abstract
    Private

    Field _pos:Vector2D
    Field _center:Vector2D
    Field _size:Vector2D

    Public

    Method CenterX:Void(entity:Sizeable)
        pos.x = entity.center.x - center.x
    End

    Method CenterY:Void(entity:Sizeable)
        pos.y = entity.center.y - center.y
    End

    Method Center:Void(entity:Sizeable)
        pos = entity.center.Copy().Sub(center)
    End

    Method pos:Vector2D() Property
        If _pos = Null Then Error("Position not set yet.")
        Return _pos
    End

    Method pos:Void(newPos:Vector2D) Property
        _pos = newPos
    End

    Method size:Vector2D() Property
        If _size = Null Then Error("Size not set yet.")
        Return _size
    End

    Method size:Void(newSize:Vector2D) Property
        _size = newSize
        _center = newSize.Copy().Div(2)
    End

    Method center:Vector2D() Property
        If _center = Null Then Error("No size set and center therefore unset.")
        Return _center
    End
End

Strict

Private

Import director
Import directorevents
Import fanout
Import keyevent
Import partial
Import positionable
Import sizeable
Import touchevent
Import vector2d

Public

Class BaseObject Extends Partial Implements Positionable, Sizeable Abstract
    Private

    Field _pos:Vector2D
    Field _center:Vector2D
    Field _size:Vector2D
    Field _layer:FanOut = New FanOut()

    Public

    Method CenterX:Void(entity:Sizeable)
        pos.x = entity.center.x - center.x
    End

    Method CenterY:Void(entity:Sizeable)
        pos.y = entity.center.y - center.y
    End

    Method Center:Void(entity:Sizeable)
        pos = entity.center.Copy().Sub(center)
    End

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

    Method OnLoading:Void()
        Super.OnLoading()
        _layer.OnLoading()
    End

    Method OnUpdate:Void(delta:Float, frameTime:Float)
        Super.OnUpdate(delta, frameTime)
        _layer.OnUpdate(delta, frameTime)
    End

    Method OnRender:Void()
        Super.OnRender()
        _layer.OnRender()
    End

    Method OnSuspend:Void()
        Super.OnSuspend()
        _layer.OnSuspend()
    End

    Method OnResume:Void(delta:Int)
        Super.OnResume(delta)
        _layer.OnResume(delta)
    End

    Method OnKeyDown:Void(event:KeyEvent)
        Super.OnKeyDown(event)
        _layer.OnKeyDown(event)
    End

    Method OnKeyPress:Void(event:KeyEvent)
        Super.OnKeyPress(event)
        _layer.OnKeyPress(event)
    End

    Method OnKeyUp:Void(event:KeyEvent)
        Super.OnKeyUp(event)
        _layer.OnKeyUp(event)
    End

    Method OnTouchDown:Void(event:TouchEvent)
        Super.OnTouchDown(event)
        _layer.OnTouchDown(event)
    End

    Method OnTouchMove:Void(event:TouchEvent)
        Super.OnTouchMove(event)
        _layer.OnTouchMove(event)
    End

    Method OnTouchUp:Void(event:TouchEvent)
        Super.OnTouchUp(event)
        _layer.OnTouchUp(event)
    End

    Method layer:FanOut() Property
        Return _layer
    End

    Method pos:Vector2D() Property
        If _pos = Null Then Error("Position not set yet.")
        Return _pos
    End

    Method pos:Void(newPos:Vector2D) Property
        _pos = newPos
    End

    Method size:Vector2D() Property
        If _size = Null Then Error("Size not set yet.")
        Return _size
    End

    Method size:Void(newSize:Vector2D) Property
        _size = newSize
        _center = newSize.Copy().Div(2)
    End

    Method center:Vector2D() Property
        If _center = Null Then Error("No size set and center therefore unset.")
        Return _center
    End
End


Slightly altered the touch example.

Slightly altered the touch example.

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

Strict

Import diddy

Function Main:Int()
	game = new MyGame()
	Return 0
End Function

Global gameScreen:GameScreen

Class MyGame extends DiddyApp
	Method OnCreate:Int()
		Super.OnCreate()
		' enable touch for non-phones
		#If TARGET<>"ios" or TARGET<>"android"
			game.inputCache.MonitorTouch(True)
		#End
		gameScreen = new GameScreen
		gameScreen.PreStart()
		return 0
	End
End

Class GameScreen Extends Screen
	Const CIRCLE_RADIUS:Float = 20
	Const DEFAULT_LIFE:Float = 2000
	Const EPSILON:Float = 3
	Field x:Float[32]
	Field y:Float[32]
	Field vx:Float[32]
	Field vy:Float[32]
	Field life:Float[32]
	Field down:Bool[32]
	Field red:Int, green:Int, blue:Int
	Field useBigCircles:Bool

	Method New()
		name = "Game"
	End

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

	Method Render:Void()
		' fill the background with an awful colour
		Cls(red, green, blue)
		' for each of the circles
		For Local i:Int = 0 Until life.Length
			' if we're touching the screen for this circle, or it has some life left
			If life[i]>0 Or down[i] Then
				' if we're touching the screen or its life is over half
				If down[i] Or life[i] > DEFAULT_LIFE/2 Then
					' draw at full alpha
					SetAlpha(1)
				Else
					' otherwise fade it out
					SetAlpha(life[i]/(DEFAULT_LIFE/2))
				End
				' draw a white circle and reset the alpha
				SetColor(255,255,255)
				If i=1 And useBigCircles Then
					DrawCircle(x[i],y[i],2*CIRCLE_RADIUS)
				Else
					DrawCircle(x[i],y[i],CIRCLE_RADIUS)
				End
				SetAlpha(1)
			End
		Next
		SetColor(255,255,255)
		DrawInfo()
	End

	Method DrawInfo:Void()
		DrawText("Diddy's Touch Test", 0, 0)
		DrawText("Press to move the circle and release to 'fling' it!", 0, 15)
		DrawText("Press down for a few seconds to change the background colour", 0, 30)
		DrawText("Do two touches to switch between big and small circles just for that fling", 0, 45)
		FPSCounter.Draw(0, 60)
	End

	Method Update:Void()
		If KeyHit(KEY_ESCAPE)
			game.screenFade.Start(50, true)
			game.nextScreen = game.exitScreen
		End
		' for each of the circles
		For Local i:Int = 0 Until life.Length
			' if we're not touching the screen for this circle and it has some life left
			If Not down[i] And life[i]>0 Then
				' slow down the velocity
				vx[i] *= 0.95
				vy[i] *= 0.95

				' move the circles
				x[i] += vx[i]*dt.frametime/1000
				y[i] += vy[i]*dt.frametime/1000

				' dodgy bouncing code
				If x[i]<0 Then
					x[i] *= -1
					vx[i] *= -1
				End
				If x[i]>SCREEN_WIDTH Then
					x[i] = 2*SCREEN_WIDTH - x[i]
					vx[i] *= -1
				End
				If y[i]<0 Then
					y[i] *= -1
					vy[i] *= -1
				End
				If y[i]>SCREEN_HEIGHT Then
					y[i] = 2*SCREEN_HEIGHT - y[i]
					vy[i] *= -1
				End
				If vx[i]*vx[i]+vy[i]*vy[i]<=EPSILON*EPSILON Then
					vx[i] = 0
					vy[i] = 0
				End

				' if it's stopped moving
				If vx[i]=0 And vy[i]=0 Then
					' decrease the life
					life[i] -= dt.frametime
					' clip it to 0
					If life[i] < 0 Then life[i] = 0
				End
			End
		Next
	End

	Method OnTouchHit:Void(x:Int, y:Int, pointer:Int)
		' if we touched, update the position of the circle for this pointer and mark it as down
		Self.x[pointer] = x
		Self.y[pointer] = y
		Self.vx[pointer] = 0
		Self.vy[pointer] = 0
		Self.down[pointer] = True
	End

	Method OnTouchReleased:Void(x:Int, y:Int, pointer:Int)
		' if we dragged, update the position of the circle for this pointer, and prepare it to fade out
		Self.x[pointer] = x
		Self.y[pointer] = y
		Self.down[pointer] = False
		Self.life[pointer] = DEFAULT_LIFE
	End

	Method OnTouchDragged:Void(x:Int, y:Int, dx:Int, dy:Int, pointer:Int)
		' if we dragged, update the position of the circle for this pointer
		Self.x[pointer] = x
		Self.y[pointer] = y
	End

	Method OnTouchFling:Void(releaseX:Int, releaseY:Int, velocityX:Float, velocityY:Float, velocitySpeed:Float, pointer:Int)
		' if we fling, update the position and velocity of the circle for this pointer
		Self.x[pointer] = releaseX
		Self.y[pointer] = releaseY
		Self.vx[pointer] = velocityX
		Self.vy[pointer] = velocityY
	End

	Method OnTouchLongPress:Void(x:Int, y:Int, pointer:Int)
		' if we long press, change the background colour to something random
		red = Rnd(0,255)
		green = Rnd(0,255)
		blue = Rnd(0,255)
		If red+green+blue < 192 Then
			red = Rnd(128,255)
			green = Rnd(128,255)
			blue = Rnd(128,255)
		End
	End

	Method OnTouchClick:Void(x:Int, y:Int, pointer:Int)
		' if we clicked with the second finger, switch between big and small circles just for that one
		If pointer = 1 Then useBigCircles = Not useBigCircles
	End
End

Strict

Import diddy

Function Main:Int()
	game = new MyGame()
	Return 0
End Function

Global gameScreen:GameScreen

Class MyGame extends DiddyApp
	Method OnCreate:Int()
		Super.OnCreate()
		gameScreen = new GameScreen
		gameScreen.PreStart()
		return 0
	End
End

Class GameScreen Extends Screen
	Const CIRCLE_RADIUS:Float = 20
	Const DEFAULT_LIFE:Float = 2000
	Const EPSILON:Float = 3
	Field x:Float[32]
	Field y:Float[32]
	Field vx:Float[32]
	Field vy:Float[32]
	Field life:Float[32]
	Field down:Bool[32]
	Field red:Int, green:Int, blue:Int
	Field useBigCircles:Bool

	Method New()
		name = "Game"
	End

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

	Method Render:Void()
		Cls
		' fill the background with an awful colour
		SetColor(red,green,blue)
		DrawRect(0,0,SCREEN_WIDTH,SCREEN_HEIGHT)
		' for each of the circles
		For Local i:Int = 0 Until life.Length
			' if we're touching the screen for this circle, or it has some life left
			If life[i]>0 Or down[i] Then
				' if we're touching the screen or its life is over half
				If down[i] Or life[i] > DEFAULT_LIFE/2 Then
					' draw at full alpha
					SetAlpha(1)
				Else
					' otherwise fade it out
					SetAlpha(life[i]/(DEFAULT_LIFE/2))
				End
				' draw a white circle and reset the alpha
				SetColor(255,255,255)
				If i=1 And useBigCircles Then
					DrawCircle(x[i],y[i],2*CIRCLE_RADIUS)
				Else
					DrawCircle(x[i],y[i],CIRCLE_RADIUS)
				End
				SetAlpha(1)
			End
		Next
		SetColor(255,255,255)
		FPSCounter.Draw(0,0)
	End

	Method Update:Void()
		If KeyHit(KEY_ESCAPE)
			game.screenFade.Start(50, true)
			game.nextScreen = game.exitScreen
		End
		' for each of the circles
		For Local i:Int = 0 Until life.Length
			' if we're not touching the screen for this circle and it has some life left
			If Not down[i] And life[i]>0 Then
				' slow down the velocity
				vx[i] *= 0.95
				vy[i] *= 0.95

				' move the circles
				x[i] += vx[i]*dt.frametime/1000
				y[i] += vy[i]*dt.frametime/1000

				' dodgy bouncing code
				If x[i]<0 Then
					x[i] *= -1
					vx[i] *= -1
				End
				If x[i]>SCREEN_WIDTH Then
					x[i] = 2*SCREEN_WIDTH - x[i]
					vx[i] *= -1
				End
				If y[i]<0 Then
					y[i] *= -1
					vy[i] *= -1
				End
				If y[i]>SCREEN_HEIGHT Then
					y[i] = 2*SCREEN_HEIGHT - y[i]
					vy[i] *= -1
				End
				If vx[i]*vx[i]+vy[i]*vy[i]<=EPSILON*EPSILON Then
					vx[i] = 0
					vy[i] = 0
				End

				' if it's stopped moving
				If vx[i]=0 And vy[i]=0 Then
					' decrease the life
					life[i] -= dt.frametime
					' clip it to 0
					If life[i] < 0 Then life[i] = 0
				End
			End
		Next
	End

	Method OnTouchHit:Void(x:Int, y:Int, pointer:Int)
		' if we touched, update the position of the circle for this pointer and mark it as down
		Self.x[pointer] = x
		Self.y[pointer] = y
		Self.vx[pointer] = 0
		Self.vy[pointer] = 0
		Self.down[pointer] = True
	End

	Method OnTouchReleased:Void(x:Int, y:Int, pointer:Int)
		' if we dragged, update the position of the circle for this pointer, and prepare it to fade out
		Self.x[pointer] = x
		Self.y[pointer] = y
		Self.down[pointer] = False
		Self.life[pointer] = DEFAULT_LIFE
	End

	Method OnTouchDragged:Void(x:Int, y:Int, dx:Int, dy:Int, pointer:Int)
		' if we dragged, update the position of the circle for this pointer
		Self.x[pointer] = x
		Self.y[pointer] = y
	End

	Method OnTouchFling:Void(releaseX:Int, releaseY:Int, velocityX:Float, velocityY:Float, velocitySpeed:Float, pointer:Int)
		' if we fling, update the position and velocity of the circle for this pointer
		Self.x[pointer] = releaseX
		Self.y[pointer] = releaseY
		Self.vx[pointer] = velocityX
		Self.vy[pointer] = velocityY
	End

	Method OnTouchLongPress:Void(x:Int, y:Int, pointer:Int)
		' if we long press, change the background colour to something random
		red = Rnd(0,255)
		green = Rnd(0,255)
		blue = Rnd(0,255)
		If red+green+blue < 192 Then
			red = Rnd(128,255)
			green = Rnd(128,255)
			blue = Rnd(128,255)
		End
	End

	Method OnTouchClick:Void(x:Int, y:Int, pointer:Int)
		' if we clicked with the second finger, switch between big and small circles just for that one
		If pointer = 1 Then useBigCircles = Not useBigCircles
	End
End


add InvalidConstructorException to PaymentManager

Strict

Private

Import bono
Import bono.src.payment.paymentprovider
Import bono.src.payment.paymentprovideralias

Public

Class PaymentManager Implements PaymentProvider
    Private

    Field provider:PaymentProvider
    Global instance:PaymentManager

    Public

    Field idPrefix:String = ""
    Field idAlias:StringMap<String> = New StringMap<String>()

    Method New()
        Throw New InvalidConstructorException("use New(PaymentProvider)")
    End

    Method New(provider:PaymentProvider)
        Self.provider = provider
    End

    Method IsPurchased:Bool(id:String)
        Return GetProvider().IsPurchased(ResolveId(id))
    End

    Method Purchase:Void(id:String)
        DebugLog("Purchase", ResolveId(id))
        GetProvider().Purchase(ResolveId(id))
    End

    Method IsProcessing:Bool()
        Return GetProvider().IsProcessing()
    End

    Method RestorePreviousPurchases:Void()
        DebugLog("RestorePreviousPurchases")
        #If TARGET="ios"
        PaymentProviderAlias(GetProvider()).RestorePreviousPurchases()
        #End
    End

    Method SetPublicKey:Void(key:String)
        DebugLog("SetPublicKey")
        #If TARGET="android" And BONO_ANDROID_MARKET="google"
        PaymentProviderAlias(GetProvider()).publicKey = key
        #End
    End

    Method SetItemGroupId:Void(itemGroupId:String)
        DebugLog("SetItemGroupId")
        #If TARGET="android" And BONO_ANDROID_MARKET="samsung"
        PaymentProviderAlias(GetProvider()).itemGroupId = itemGroupId
        #End
    End

    Method GetProvider:PaymentProvider()
        Return provider
    End

    Function GetInstance:PaymentManager()
        If Not instance
            instance = New PaymentManager(New PaymentProviderAlias())
        End

        Return instance
    End

    Private

    Method ResolveId:String(id:String)
        If idAlias.Contains(id)
            Return idPrefix + idAlias.Get(id)
        Else
            Return idPrefix + id
        End
    End

    Method DebugLog:Void(func:String, message:String="")
        #If CONFIG="debug"
        Print "[PaymentManager " + func + "] " + message
        #End
    End
End

Strict

Private

Import bono.src.payment.paymentprovider
Import bono.src.payment.paymentprovideralias

Public

Class PaymentManager Implements PaymentProvider
    Private

    Field provider:PaymentProvider
    Global instance:PaymentManager

    Public

    Field idPrefix:String = ""
    Field idAlias:StringMap<String> = New StringMap<String>()

    Method New(provider:PaymentProvider)
        Self.provider = provider
    End

    Method IsPurchased:Bool(id:String)
        Return GetProvider().IsPurchased(ResolveId(id))
    End

    Method Purchase:Void(id:String)
        DebugLog("Purchase", ResolveId(id))
        GetProvider().Purchase(ResolveId(id))
    End

    Method IsProcessing:Bool()
        Return GetProvider().IsProcessing()
    End

    Method RestorePreviousPurchases:Void()
        DebugLog("RestorePreviousPurchases")
        #If TARGET="ios"
        PaymentProviderAlias(GetProvider()).RestorePreviousPurchases()
        #End
    End

    Method SetPublicKey:Void(key:String)
        DebugLog("SetPublicKey")
        #If TARGET="android" And BONO_ANDROID_MARKET="google"
        PaymentProviderAlias(GetProvider()).publicKey = key
        #End
    End

    Method SetItemGroupId:Void(itemGroupId:String)
        DebugLog("SetItemGroupId")
        #If TARGET="android" And BONO_ANDROID_MARKET="samsung"
        PaymentProviderAlias(GetProvider()).itemGroupId = itemGroupId
        #End
    End

    Method GetProvider:PaymentProvider()
        Return provider
    End

    Function GetInstance:PaymentManager()
        If Not instance
            instance = New PaymentManager(New PaymentProviderAlias())
        End

        Return instance
    End

    Private

    Method ResolveId:String(id:String)
        If idAlias.Contains(id)
            Return idPrefix + idAlias.Get(id)
        Else
            Return idPrefix + id
        End
    End

    Method DebugLog:Void(func:String, message:String="")
        #If CONFIG="debug"
        Print "[PaymentManager " + func + "] " + message
        #End
    End
End


Fixed a problem with miscalculations for dynamic cols/rows.

Strict

Import core

Interface ILayoutManager
	Method LayoutMinimum:Point(parent:Component, point:Point=Null)
	Method Layout:Point(parent:Component, point:Point=Null)
End

Interface ILayoutData
End

' Based on SWT GridLayout
Class GridLayout Implements ILayoutManager
Private
' Private Constants
	Const DYNAMIC_MAX:Int = 100

' Private fields

	Field columns:Int
	Field rows:Int
	Field calculatedRows:Int
	Field calculatedColumns:Int

	' cached stuff
	Field compArray:Object[] = []
	Field rowNums:Int[] = []
	Field colNums:Int[] = []
	Field cellTaken:Bool[] = []

Public
' Public constants

	Const FILLTYPE_FILL:Int = 0
	Const FILLTYPE_PREFERRED:Int = 1
	Const FILLTYPE_STATIC:Int = 2

	Const DEFAULT_HORIZONTAL_MARGIN:Int = 5
	Const DEFAULT_VERTICAL_MARGIN:Int = 5
	Const DEFAULT_HORIZONTAL_SPACING:Int = 5
	Const DEFAULT_VERTICAL_SPACING:Int = 5

' Public fields

	Field colWidthTypes:Int[]
	Field rowHeightTypes:Int[]
	Field colWidthValues:Int[]
	Field rowHeightValues:Int[]

	Field leftMargin:Int
	Field rightMargin:Int
	Field topMargin:Int
	Field bottomMargin:Int
	Field horizontalSpacing:Int
	Field verticalSpacing:Int

' Constructors

	Method New(rows:Int=0, columns:Int=1,
			leftMargin:Int=DEFAULT_HORIZONTAL_MARGIN, rightMargin:Int=DEFAULT_HORIZONTAL_MARGIN,
			topMargin:Int=DEFAULT_VERTICAL_MARGIN, bottomMargin:Int=DEFAULT_VERTICAL_MARGIN,
			horizontalSpacing:Int=DEFAULT_HORIZONTAL_SPACING, verticalSpacing:Int=DEFAULT_VERTICAL_SPACING)
		If rows <= 0 And columns <= 0 Then AssertError("Only one of rows/columns may be dynamic.")
		Self.rows = rows
		Self.columns = columns
		Self.leftMargin = leftMargin
		Self.rightMargin = rightMargin
		Self.topMargin = topMargin
		Self.bottomMargin = bottomMargin
		Self.horizontalSpacing = horizontalSpacing
		Self.verticalSpacing = verticalSpacing

		If rows = 0 Then
			rowHeightTypes = New Int[DYNAMIC_MAX]
			rowHeightValues = New Int[DYNAMIC_MAX]
		Else
			rowHeightTypes = New Int[rows]
			rowHeightValues = New Int[rows]
		End

		If columns = 0 Then
			colWidthTypes = New Int[DYNAMIC_MAX]
			colWidthValues = New Int[DYNAMIC_MAX]
		Else
			colWidthTypes = New Int[columns]
			colWidthValues = New Int[columns]
		End

		For Local i:Int = 0 Until rowHeightValues.Length
			rowHeightValues[i] = -1
			rowHeightTypes[i] = FILLTYPE_FILL
		Next

		For Local i:Int = 0 Until colWidthValues.Length
			colWidthValues[i] = -1
			colWidthTypes[i] = FILLTYPE_FILL
		Next
	End

' Implements ILayoutManager

	Method Layout:Point(parent:Component, point:Point=Null)
		Return LayoutDelegate(parent, False, point)
	End

	Method LayoutMinimum:Point(parent:Component, point:Point=Null)
		Return LayoutDelegate(parent, True, point)
	End

Private
' Private methods
	Method LayoutDelegate:Point(parent:Component, calculateMinimum:Bool, point:Point=Null)
		' get the children (reuse the array)
		Local compCount:Int = 0
		If compArray.Length < parent.Children.Size Then
			compArray = parent.Children.ToArray()
			compCount = compArray.Length
		Else
			compCount = parent.Children.FillArray(compArray)
		End

		' create or clear rowNums array
		If rowNums.Length < compCount Then rowNums = New Int[compCount]
		For Local i:Int = 0 Until rowNums.Length
			rowNums[i] = 0
		Next

		' create or clear colNums array
		If colNums.Length < compCount Then colNums = New Int[compCount]
		For Local i:Int = 0 Until colNums.Length
			colNums[i] = 0
		Next

		' create or clear cellTaken array
		Local cellTakenCols:Int = columns
		Local cellTakenRows:Int = rows
		If cellTakenCols = 0 Then cellTakenCols = DYNAMIC_MAX
		If cellTakenRows = 0 Then cellTakenRows = DYNAMIC_MAX
		If cellTaken.Length < cellTakenCols*cellTakenRows Then cellTaken = New Bool[cellTakenCols*cellTakenRows]
		For Local i:Int = 0 Until cellTaken.Length
			cellTaken[i] = False
		Next

		' loop on components
		Local currentRow:Int = 0, currentColumn:Int = 0
		Local maxRow:Int = rows - 1, maxColumn:Int = columns - 1
		Local colSpan:Int = 1, rowSpan:Int = 1
		For Local i:Int = 0 Until compCount
			' get the data object
			Local data:GridData = Null
			If GridData(Component(compArray[i]).LayoutData) <> Null Then
				data = GridData(Component(compArray[i]).LayoutData)
				colSpan = data.colSpan
				rowSpan = data.rowSpan
			Else
				colSpan = 1
				rowSpan = 1
			End

			' reset current row/col to last found spot
			If i > 0 Then
				currentRow = rowNums[i-1]
				currentColumn = colNums[i-1]
			End

			' find the next available spot
			If cellTaken[currentColumn+currentRow*cellTakenCols] Then
				Repeat
					' if columns are dynamic, top to bottom then left to right
					If columns <= 0 Then
						currentRow += 1
						If currentRow >= rows Then
							currentRow = 0
							currentColumn += 1
							maxColumn = Max(maxColumn,currentColumn)
						End
					' if rows are dynamic, or the grid is fixed, left to right then top to bottom
					Else
						currentColumn += 1
						If currentColumn >= columns Then
							currentColumn = 0
							currentRow += 1
							maxRow = Max(maxRow,currentRow)
						End
					End
				Until Not cellTaken[currentColumn+currentRow*cellTakenCols]
			End
			' set the component's location to that
			rowNums[i] = currentRow
			colNums[i] = currentColumn
			If data <> Null Then
				data.startRow = currentRow
				data.startCol = currentColumn
			End

			' fill in the boolean array
			For Local fillCol:Int = currentColumn Until currentColumn + colSpan
				For Local fillRow:Int = currentRow Until currentRow + rowSpan
					cellTaken[fillCol + fillRow*cellTakenCols] = True
				Next
			Next
		Next

		' fix the dynamic one (if there is one)
		If maxRow < 0 Then maxRow = currentRow + rowSpan
		If maxColumn < 0 Then maxColumn = currentColumn + colSpan

		' store last
		calculatedRows = maxRow + 1
		calculatedColumns = maxColumn + 1

		' reset rows and columns marked as preferred
		For Local i:Int = 0 Until calculatedRows
			If rowHeightTypes[i] = FILLTYPE_PREFERRED Then rowHeightValues[i] = 0
		Next
		For Local i:Int = 0 Until calculatedColumns
			If colWidthTypes[i] = FILLTYPE_PREFERRED Then colWidthValues[i] = 0
		Next

		' now set the preferred sizes of each row and column based on component
		For Local i:Int = 0 Until compCount
			' get the data object
			Local data:GridData = Null
			If GridData(Component(compArray[i]).LayoutData) <> Null Then
				data = GridData(Component(compArray[i]).LayoutData)
				colSpan = data.colSpan
				rowSpan = data.rowSpan
			Else
				colSpan = 1
				rowSpan = 1
			End

			' if the row for this component is preferred, store the max of the heights
			If rowHeightTypes[rowNums[i]] = FILLTYPE_PREFERRED Then
				' only continue if the component consumes a single row
				If rowSpan = 1 Then
					Local y:Int = Component(compArray[i]).PreferredHeight
					If y <= 0 Then y = Component(compArray[i]).MinimumHeight
					rowHeightValues[rowNums[i]] = Max(rowHeightValues[rowNums[i]], y)
				End
			End

			' if the column for this component is preferred, store the max of the widths
			If colWidthTypes[colNums[i]] = FILLTYPE_PREFERRED Then
				' only continue if the component consumes a single column
				If colSpan = 1 Then
					Local x:Int = Component(compArray[i]).PreferredWidth
					If x <= 0 Then x = Component(compArray[i]).MinimumWidth
					colWidthValues[colNums[i]] = Max(colWidthValues[colNums[i]], x)
				End
			End
		Next

		' calculate the available fill space
		Local colFillCount:Int = 0
		Local rowFillCount:Int = 0
		Local fillWidth:Int = parent.Width - leftMargin - rightMargin - horizontalSpacing * (calculatedColumns - 1)
		Local fillHeight:Int = parent.Height - topMargin - bottomMargin - verticalSpacing * (calculatedRows - 1)
		For Local i:Int = 0 Until calculatedColumns
			If colWidthTypes[i] = FILLTYPE_FILL Then
				colFillCount += 1
			Else
				fillWidth -= colWidthValues[i]
			End
		Next
		For Local i:Int = 0 Until calculatedRows
			If rowHeightTypes[i] = FILLTYPE_FILL Then
				rowFillCount += 1
			Else
				fillHeight -= rowHeightValues[i]
			End
		Next

		' set the fill sizes
		For Local i:Int = 0 Until calculatedColumns
			If colWidthTypes[i] = FILLTYPE_FILL Then
				If calculateMinimum Then colWidthValues[i] = 0 Else colWidthValues[i] = fillWidth / colFillCount
			End
		Next
		For Local i:Int = 0 Until calculatedRows
			If rowHeightTypes[i] = FILLTYPE_FILL Then
				If calculateMinimum Then rowHeightValues[i] = 0 Else rowHeightValues[i] = fillHeight / rowFillCount
			End
		Next

		' if not getting minimum, do the layout
		If Not calculateMinimum Then
			For Local i:Int = 0 Until compCount
				' get the data object
				Local data:GridData = Null
				If GridData(Component(compArray[i]).LayoutData) <> Null Then
					data = GridData(Component(compArray[i]).LayoutData)
					colSpan = data.colSpan
					rowSpan = data.rowSpan
				Else
					colSpan = 1
					rowSpan = 1
				End

				Local x:Int = leftMargin
				Local y:Int = topMargin
				For Local j:Int = 0 Until colNums[i]
					x += colWidthValues[j] + horizontalSpacing
				Next
				For Local j:Int = 0 Until rowNums[i]
					y += rowHeightValues[j] + verticalSpacing
				Next

				Local width:Int = colWidthValues[colNums[i]]
				Local height:Int = rowHeightValues[rowNums[i]]
				For Local j:Int = 1 Until colSpan
					AssertLessThanInt(i, colNums.Length)
					AssertLessThanInt(colNums[i]+j, colWidthValues.Length)
					width += colWidthValues[colNums[i]+j] + horizontalSpacing
				Next
				For Local j:Int = 1 Until rowSpan
					AssertLessThanInt(i, rowNums.Length)
					AssertLessThanInt(rowNums[i]+j, rowHeightValues.Length)
					height += rowHeightValues[rowNums[i]+j] + verticalSpacing
				Next

				Component(compArray[i]).SetBounds(x, y, width, height)
			Next
		End

		' calculate the total size of the laid-out container
		If point = Null Then point = New Point
		For Local i:Int = 0 Until calculatedColumns
			If i = 0 Then point.x = leftMargin Else point.x += horizontalSpacing
			point.x += colWidthValues[i]
		Next
		point.x += rightMargin

		For Local i:Int = 0 Until calculatedRows
			If i = 0 Then point.y = topMargin Else point.y += verticalSpacing
			point.y += rowHeightValues[i]
		Next
		point.y += bottomMargin

		For Local i:Int = 0 Until compArray.Length
			compArray[i] = Null
		Next

		Return point
	End
End ' Class GridLayout

Class GridData Implements ILayoutData
Public
' Public fields

	Field startRow:Int = -1
	Field startCol:Int = -1
	Field rowSpan:Int = 1
	Field colSpan:Int = 1
	Field topBorder:Int = 0
	Field bottomBorder:Int = 0
	Field leftBorder:Int = 0
	Field rightBorder:Int = 0

' Constructors

	Method New(rowSpan:Int=1,colSpan:Int=1, border:Int=0)
		Self.rowSpan = rowSpan
		Self.colSpan = colSpan
		Self.topBorder = border
		Self.bottomBorder = border
		Self.leftBorder = border
		Self.rightBorder = border
	End
End ' Class GridData

Strict

Import core

Interface ILayoutManager
	Method LayoutMinimum:Point(parent:Component, point:Point=Null)
	Method Layout:Point(parent:Component, point:Point=Null)
End

Interface ILayoutData
End

Class GridLayout Implements ILayoutManager
Private
' Private Constants
	Const DYNAMIC_MAX:Int = 100

' Private fields

	Field columns:Int
	Field rows:Int
	Field calculatedRows:Int
	Field calculatedColumns:Int

	' cached stuff
	Field compArray:Object[] = []
	Field rowNums:Int[] = []
	Field colNums:Int[] = []
	Field cellTaken:Bool[] = []

Public
' Public constants

	Const FILLTYPE_FILL:Int = 0
	Const FILLTYPE_PREFERRED:Int = 1
	Const FILLTYPE_STATIC:Int = 2

	Const DEFAULT_HORIZONTAL_MARGIN:Float = 5
	Const DEFAULT_VERTICAL_MARGIN:Float = 5
	Const DEFAULT_HORIZONTAL_SPACING:Float = 5
	Const DEFAULT_VERTICAL_SPACING:Float = 5

' Public fields

	Field colWidthTypes:Int[]
	Field rowHeightTypes:Int[]
	Field colWidthValues:Float[]
	Field rowHeightValues:Float[]

	Field leftMargin:Float
	Field rightMargin:Float
	Field topMargin:Float
	Field bottomMargin:Float
	Field horizontalSpacing:Float
	Field verticalSpacing:Float

' Constructors

	Method New(rows:Int=0, columns:Int=1,
			leftMargin:Float=DEFAULT_HORIZONTAL_MARGIN, rightMargin:Float=DEFAULT_HORIZONTAL_MARGIN,
			topMargin:Float=DEFAULT_VERTICAL_MARGIN, bottomMargin:Float=DEFAULT_VERTICAL_MARGIN,
			horizontalSpacing:Float=DEFAULT_HORIZONTAL_SPACING, verticalSpacing:Float=DEFAULT_VERTICAL_SPACING)
		If rows <= 0 And columns <= 0 Then Error("Only one of rows/columns may be dynamic.")
		Self.rows = rows
		Self.columns = columns
		Self.leftMargin = leftMargin
		Self.rightMargin = rightMargin
		Self.topMargin = topMargin
		Self.bottomMargin = bottomMargin
		Self.horizontalSpacing = horizontalSpacing
		Self.verticalSpacing = verticalSpacing

		If rows = 0 Then
			rowHeightTypes = New Int[DYNAMIC_MAX]
			rowHeightValues = New Float[DYNAMIC_MAX]
		Else
			rowHeightTypes = New Int[rows]
			rowHeightValues = New Float[rows]
		End

		If columns = 0 Then
			colWidthTypes = New Int[DYNAMIC_MAX]
			colWidthValues = New Float[DYNAMIC_MAX]
		Else
			colWidthTypes = New Int[columns]
			colWidthValues = New Float[columns]
		End

		For Local i:Int = 0 Until rowHeightValues.Length
			rowHeightValues[i] = -1
			rowHeightTypes[i] = FILLTYPE_FILL
		Next

		For Local i:Int = 0 Until colWidthValues.Length
			colWidthValues[i] = -1
			colWidthTypes[i] = FILLTYPE_FILL
		Next
	End

' Implements ILayoutManager

	Method Layout:Point(parent:Component, point:Point=Null)
		Return LayoutDelegate(parent, False, point)
	End

	Method LayoutMinimum:Point(parent:Component, point:Point=Null)
		Return LayoutDelegate(parent, True, point)
	End

Private
' Private methods
	Method LayoutDelegate:Point(parent:Component, calculateMinimum:Bool, point:Point=Null)
		' get the children (reuse the array)
		Local compCount:Int = 0
		If compArray.Length < parent.Children.Size Then
			compArray = parent.Children.ToArray()
			compCount = compArray.Length
		Else
			compCount = parent.Children.FillArray(compArray)
		End

		' create or clear rowNums array
		If rowNums.Length < compCount Then rowNums = New Int[compCount]
		For Local i:Int = 0 Until rowNums.Length
			rowNums[i] = 0
		Next

		' create or clear colNums array
		If colNums.Length < compCount Then colNums = New Int[compCount]
		For Local i:Int = 0 Until colNums.Length
			colNums[i] = 0
		Next

		' create or clear cellTaken array
		Local cellTakenCols:Int = columns
		Local cellTakenRows:Int = rows
		If cellTakenCols = 0 Then cellTakenCols = DYNAMIC_MAX
		If cellTakenRows = 0 Then cellTakenRows = DYNAMIC_MAX
		If cellTaken.Length < cellTakenCols*cellTakenRows Then cellTaken = New Bool[cellTakenCols*cellTakenRows]
		For Local i:Int = 0 Until cellTaken.Length
			cellTaken[i] = False
		Next

		' loop on components
		Local currentRow:Int = 0, currentColumn:Int = 0
		Local maxRow:Int = rows - 1, maxColumn:Int = columns - 1
		Local colSpan:Int = 1, rowSpan:Int = 1
		For Local i:Int = 0 Until compCount
			' get the data object
			Local data:GridData = Null
			If GridData(Component(compArray[i]).LayoutData) <> Null Then
				data = GridData(Component(compArray[i]).LayoutData)
				colSpan = data.colSpan
				rowSpan = data.rowSpan
			Else
				colSpan = 1
				rowSpan = 1
			End

			' reset current row/col to last found spot
			If i > 0 Then
				currentRow = rowNums[i-1]
				currentColumn = colNums[i-1]
			End

			' find the next available spot
			If cellTaken[currentColumn+currentRow*cellTakenCols] Then
				Repeat
					' if columns are dynamic, top to bottom then left to right
					If columns <= 0 Then
						currentRow += 1
						If currentRow >= rows Then
							currentRow = 0
							currentColumn += 1
							maxColumn = currentColumn
						End
					' if rows are dynamic, or the grid is fixed, left to right then top to bottom
					Else
						currentColumn += 1
						If currentColumn >= columns Then
							currentColumn = 0
							currentRow += 1
							maxRow = currentRow
						End
					End
				Until Not cellTaken[currentColumn+currentRow*cellTakenCols]
			End
			' set the component's location to that
			rowNums[i] = currentRow
			colNums[i] = currentColumn
			If data <> Null Then
				data.startRow = currentRow
				data.startCol = currentColumn
			End

			' fill in the boolean array
			For Local fillCol:Int = currentColumn Until currentColumn + colSpan
				For Local fillRow:Int = currentRow Until currentRow + rowSpan
					cellTaken[fillCol + fillRow*cellTakenCols] = True
				Next
			Next
		Next

		' store last
		calculatedRows = maxRow + 1
		calculatedColumns = maxColumn + 1

		' reset rows and columns marked as preferred
		For Local i:Int = 0 Until calculatedRows
			If rowHeightTypes[i] = FILLTYPE_PREFERRED Then rowHeightValues[i] = 0
		Next
		For Local i:Int = 0 Until calculatedColumns
			If colWidthTypes[i] = FILLTYPE_PREFERRED Then colWidthValues[i] = 0
		Next

		' now set the preferred sizes of each row and column based on component
		For Local i:Int = 0 Until compCount
			' get the data object
			Local data:GridData = Null
			If GridData(Component(compArray[i]).LayoutData) <> Null Then
				data = GridData(Component(compArray[i]).LayoutData)
				colSpan = data.colSpan
				rowSpan = data.rowSpan
			Else
				colSpan = 1
				rowSpan = 1
			End

			' if the row for this component is preferred, store the max of the heights
			If rowHeightTypes[rowNums[i]] = FILLTYPE_PREFERRED Then
				' only continue if the component consumes a single row
				If rowSpan = 1 Then
					Local y:Float = Component(compArray[i]).PreferredHeight
					If y <= 0 Then y = Component(compArray[i]).MinimumHeight
					rowHeightValues[rowNums[i]] = Max(rowHeightValues[rowNums[i]], y)
				End
			End

			' if the column for this component is preferred, store the max of the widths
			If colWidthTypes[colNums[i]] = FILLTYPE_PREFERRED Then
				' only continue if the component consumes a single column
				If colSpan = 1 Then
					Local x:Float = Component(compArray[i]).PreferredWidth
					If x <= 0 Then x = Component(compArray[i]).MinimumWidth
					colWidthValues[colNums[i]] = Max(colWidthValues[colNums[i]], x)
				End
			End
		Next

		' calculate the available fill space
		Local colFillCount:Int = 0
		Local rowFillCount:Int = 0
		Local fillWidth:Float = parent.Width - leftMargin - rightMargin - horizontalSpacing * (calculatedColumns - 1)
		Local fillHeight:Float = parent.Height - topMargin - bottomMargin - verticalSpacing * (calculatedRows - 1)
		For Local i:Int = 0 Until calculatedColumns
			If colWidthTypes[i] = FILLTYPE_FILL Then
				colFillCount += 1
			Else
				fillWidth -= colWidthValues[i]
			End
		Next
		For Local i:Int = 0 Until calculatedRows
			If rowHeightTypes[i] = FILLTYPE_FILL Then
				rowFillCount += 1
			Else
				fillHeight -= rowHeightValues[i]
			End
		Next

		' set the fill sizes
		For Local i:Int = 0 Until calculatedColumns
			If colWidthTypes[i] = FILLTYPE_FILL Then
				If calculateMinimum Then colWidthValues[i] = 0 Else colWidthValues[i] = fillWidth / colFillCount
			End
		Next
		For Local i:Int = 0 Until calculatedRows
			If rowHeightTypes[i] = FILLTYPE_FILL Then
				If calculateMinimum Then rowHeightValues[i] = 0 Else rowHeightValues[i] = fillHeight / rowFillCount
			End
		Next

		' if not getting minimum, do the layout
		If Not calculateMinimum Then
			For Local i:Int = 0 Until compCount
				' get the data object
				Local data:GridData = Null
				If GridData(Component(compArray[i]).LayoutData) <> Null Then
					data = GridData(Component(compArray[i]).LayoutData)
					colSpan = data.colSpan
					rowSpan = data.rowSpan
				Else
					colSpan = 1
					rowSpan = 1
				End

				Local x:Float = leftMargin
				Local y:Float = topMargin
				For Local j:Int = 0 Until colNums[i]
					x += colWidthValues[j] + horizontalSpacing
				Next
				For Local j:Int = 0 Until rowNums[i]
					y += rowHeightValues[j] + verticalSpacing
				Next

				Local width:Float = colWidthValues[colNums[i]]
				Local height:Float = rowHeightValues[rowNums[i]]
				For Local j:Int = 1 Until colSpan
					width += colWidthValues[colNums[i]+j] + horizontalSpacing
				Next
				For Local j:Int = 1 Until rowSpan
					height += rowHeightValues[rowNums[i]+j] + verticalSpacing
				Next

				Component(compArray[i]).SetBounds(x, y, width, height)
			Next
		End

		' calculate the total size of the laid-out container
		If point = Null Then point = New Point
		For Local i:Int = 0 Until colWidthValues.Length
			If i = 0 Then point.x = leftMargin Else point.x += horizontalSpacing
			point.x += colWidthValues[i]
		Next
		point.x += rightMargin

		For Local i:Int = 0 Until rowHeightValues.Length
			If i = 0 Then point.y = topMargin Else point.y += verticalSpacing
			point.y += rowHeightValues[i]
		Next
		point.y += bottomMargin

		For Local i:Int = 0 Until compArray.Length
			compArray[i] = Null
		Next

		Return point
	End
End ' Class GridLayout

Class GridData Implements ILayoutData
Public
' Public fields

	Field startRow:Int = -1
	Field startCol:Int = -1
	Field rowSpan:Int = 1
	Field colSpan:Int = 1
	Field topBorder:Int = 0
	Field bottomBorder:Int = 0
	Field leftBorder:Int = 0
	Field rightBorder:Int = 0

' Constructors

	Method New(rowSpan:Int=1,colSpan:Int=1, border:Int=0)
		Self.rowSpan = rowSpan
		Self.colSpan = colSpan
		Self.topBorder = border
		Self.bottomBorder = border
		Self.leftBorder = border
		Self.rightBorder = border
	End
End ' Class GridData