Forgot to add the email parameter to the cpp function.

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

#if HOST="macos" And TARGET="glfw"
	Import "native/diddy.${TARGET}.mac.${LANG}"
#else
	Import "native/diddy.${TARGET}.${LANG}"
#end
Import mojo
Import assert

Extern

	#If LANG="cpp" Then
		Function RealMillisecs:Int() = "diddy::systemMillisecs"
		Function FlushKeys:Void() = "diddy::flushKeys"
		Function HideMouse:Void() = "diddy::hideMouse"
		Function ShowMouse:Void() = "diddy::showMouse"
		Function GetUpdateRate:Int() = "diddy::getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy::setMouse"
		Function ShowKeyboard:Void() = "diddy::showKeyboard"
		Function LaunchBrowser:Void(address:String) = "diddy::launchBrowser"
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "diddy::launchEmail"
	#Else
		Function RealMillisecs:Int() = "diddy.systemMillisecs"
		Function FlushKeys:Void() = "diddy.flushKeys"
		Function HideMouse:Void() = "diddy.hideMouse"
		Function ShowMouse:Void() = "diddy.showMouse"
		Function GetUpdateRate:Int() = "diddy.getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy.setMouse"
		Function ShowKeyboard:Void() = "diddy.showKeyboard"
		Function LaunchBrowser:Void(address:String) = "diddy.launchBrowser"
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "diddy.launchEmail"
	#End
Public

Function ExitApp:Void()
	Error ""
End

Function RectsOverlap:Int(x0:Float, y0:Float, w0:Float, h0:Float, x2:Float, y2:Float, w2:Float, h2:Float)
	If x0 > (x2 + w2) Or (x0 + w0) < x2 Then Return False
	If y0 > (y2 + h2) Or (y0 + h0) < y2 Then Return False
	Return True
End

Function DrawRectOutline:Void(x:Int, y:Int, w:Int, h:Int)
	w -= 1
	h -= 1
	DrawLine(x,y,x+w,y)
	DrawLine(x+w,y,x+w,y+h)
	DrawLine(x+w,y+h,x,y+h)
	DrawLine(x,y+h,x,y)
End

Function LoadBitmap:Image(path$, flags%=0)
	Local pointer:Image = LoadImage(path, 1, flags)

	AssertNotNull(pointer, "Error loading bitmap "+path)

   	Return pointer
End

Function LoadAnimBitmap:Image(path$, w%, h%, count%, tmpImage:Image)
	'tmpImage = loadBitmap(path) <-- This creates another image, decided to just copy the code here
	tmpImage = LoadImage(path)

	AssertNotNull(tmpImage, "Error loading bitmap "+path)

	Local pointer:Image = tmpImage.GrabImage( 0, 0, w, h, count, Image.MidHandle)

   	Return pointer
End

Function LoadSoundSample:Sound(path$)
	Local pointer:Sound = LoadSound(path)
	AssertNotNull(pointer, "Error loading sound "+path)
	Return pointer
End

Function FormatNumeric:String(value:Float)
	Local i:Int,s:String,ns:String,k:Int
	Local os:String
	s=String(value)
	os=s
	Local pos:Int=s.Length()
	If s.Find(".")>0 pos=s.Find(".") Else os=""
	For i=pos To 1 Step -1
		If k>2 ns+="." k=0
		k+=1
		ns=ns+Mid(s,i,1)
	Next
	s=""
	For i= ns.Length() To 1 Step -1
		s+=Mid(ns,i,1)
	Next
	If os<>"" s=s+","+os[pos+1..]
	Return s
End

Function Left$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[..n]
End

Function Right$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[str.Length()-n..]
End

Function LSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=str+rep
	Return str[..n]
End

Function RSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=rep+str
	Return str[str.Length()-n..]
End

Function Mid$( str$,pos:Int,size:Int=-1 )
	If pos>str.Length() Return ""
	pos-=1
	If( size<0 ) Return str[pos..]
	If pos<0 size=size+pos pos=0
	If pos+size>str.Length() size=str.Length()-pos
	Return str[pos..pos+size]
End

Function StripDir$( path$ )
	Local i:=path.FindLast( "/" )
	If i<>-1 Return path[i+1..]
	Return path
End

Function StripExt$( path$ )
	Local i:=path.FindLast( "." )
	If i<>-1 And path.Find( "/",i+1 )=-1 Return path[..i]
	Return path
End

Function StripAll$( path$ )
	Return StripDir( StripExt( path ) )
End

Function Round%(flot#)
	Return Floor(flot+0.5)
End

Function PointInSpot:Int(x1:Float, y1:Float, x2:Float, y2:Float, radius:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return dx * dx + dy * dy <= radius * radius
End

Function AnyInputPressed:Bool()
	For Local i:Int = 0 To 511
		If KeyHit(i) Then Return True
	Next
	Return False
End

Function FormatNumber:String(number:Float, decimal:Int=4, comma:Int=0, padleft:Int=0 )
	Assert(decimal > -1 And comma > -1 And padleft > -1, "Negative numbers not allowed in FormatNumber()")

	Local str:String = number
	Local dl:Int = str.Find(".")
	If decimal = 0 Then decimal = -1
	str = str[..dl+decimal+1]

	If comma
		While dl>comma
			str = str[..dl-comma] + "," + str[dl-comma..]
			dl -= comma
		Wend
	End

	If padleft
		Local paddedLength:Int = padleft+decimal+1
		If paddedLength < str.Length Then str = "Error"
		str = RSet(str,paddedLength)
	End
	Return str
End

Function IsWhitespace:Bool(str:String)
	Return str = "~t" Or str = "~n" Or str = "~r" Or str = " "
End

Function IsWhitespace:Bool(val:Int)
	Return val = ASC_TAB Or val = ASC_LF Or val = ASC_CR Or val = ASC_SPACE
End

' TODO: allow encoding of Int[]
' TODO: enabling lineWrap will wrap the string every lineWrapWidth characters (defaults to 80)
' enabling padOutput will pad the ending of the output with equals characters (=) to next 4-byte boundary
Function EncodeBase64:String(src:String, padOutput:Bool=False, lineWrap:Bool=False, lineWrapWidth:Int=80)
	Local rv:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	If src.Length = 0 Then Return ""
	Repeat
		s1 = src[i]
		If i+1 < src.Length Then s2 = src[i+1] Else s2 = 0
		If i+2 < src.Length Then s3 = src[i+2] Else s3 = 0
		a = s1 Shr 2
		b = ((s1 & 3) Shl 4) | (s2 Shr 4)
		c = ((s2 & 15) Shl 2) | (s3 Shr 6)
		d = s3 & 63
		If i+1 >= src.Length Then c = 64
		If i+2 >= src.Length Then d = 64
		rv += BASE64_CHARS[a..a+1] + BASE64_CHARS[b..b+1]
		If c < 64 Or padOutput Then rv += BASE64_CHARS[c..c+1]
		If d < 64 Or padOutput Then rv += BASE64_CHARS[d..d+1]
		i += 3
	Until i >= src.Length
	Return rv
End

' TODO: convert this to use BASE64_ARRAY
' I'll do this once Monkey has a decent string builder class.
Function DecodeBase64:String(src:String)
	Local rv:String = "", src2:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	' remove any non-base64 characters
	For i = 0 Until src.Length
		If BASE64_CHARS.Find(src[i..i+1]) >= 0 Then src2 += src[i..i+1]
	Next
	If src2.Length = 0 Then Return ""
	i = 0
	Repeat
		a = BASE64_CHARS.Find(src2[i..i+1])
		If i+1 > src2.Length Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_CHARS.Find(src2[i+1..i+2])
		If i+2 < src2.Length Then c = BASE64_CHARS.Find(src2[i+2..i+3]) Else c = 64
		If i+3 < src2.Length Then d = BASE64_CHARS.Find(src2[i+3..i+4]) Else d = 64
		s1 = (a Shl 2) | (b Shr 4)
		s2 = ((b & 15) Shl 4) | (c Shr 2)
		s3 = ((c & 3) Shl 6) | d
		rv += String.FromChar(s1)
		If c <> 64 Then rv += String.FromChar(s2)
		If d <> 64 Then rv += String.FromChar(s3)
		i += 4
	Until i >= src2.Length
	Return rv
End

Function DecodeBase64Bytes:Int[](src:String)
	InitBase64()
	Local a:Int, b:Int, c:Int, d:Int, i:Int, j:Int
	Local src2:Int[] = New Int[src.Length]

	' find out how many base64 characters
	Local srclen:Int = 0
	For i = 0 Until src.Length
		If BASE64_ARRAY[src[i]] >= 0 Then
			src2[srclen] = src[i]
			srclen += 1
		End
	Next

	' die if there are no base64 chars
	If srclen = 0 Then Return []

	' get the target length and create the array
	Local len:Int = 3*srclen/4
	If srclen Mod 4 = 1 Or srclen Mod 4 = 2 Then
		len += 1
	ElseIf srclen Mod 4 = 3 Then
		len += 2
	End
	Local rv:Int[] = New Int[len]

	i = 0
	j = 0
	Repeat
		a = BASE64_ARRAY[src2[i]]
		If i+1 > srclen Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_ARRAY[src2[i+1]]
		If i+2 < srclen Then c = BASE64_ARRAY[src2[i+2]] Else c = 64
		If i+3 < srclen Then d = BASE64_ARRAY[src2[i+3]] Else d = 64
		rv[j] = (a Shl 2) | (b Shr 4)
		If j+1 < len Then rv[j+1] = ((b & 15) Shl 4) | (c Shr 2)
		If j+2 < len Then rv[j+2] = ((c & 3) Shl 6) | d
		i += 4
		j += 3
	Until i >= srclen
	Return rv
End

Function InitBase64:Void()
	If BASE64_ARRAY.Length = 0 Then
		BASE64_ARRAY = New Int[256]
		Local i% = 0
		For i = 0 Until BASE64_ARRAY.Length
			BASE64_ARRAY[i] = -1
		Next
		For i = 0 Until BASE64_CHARS.Length
			BASE64_ARRAY[BASE64_CHARS[i]] = i
		Next
	End
End

' constants

Const BASE64_CHARS:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
Global BASE64_ARRAY:Int[] = []

' control characters
Const ASC_NUL:Int = 0       ' Null character
Const ASC_SOH:Int = 1       ' Start of Heading
Const ASC_STX:Int = 2       ' Start of Text
Const ASC_ETX:Int = 3       ' End of Text
Const ASC_EOT:Int = 4       ' End of Transmission
Const ASC_ENQ:Int = 5       ' Enquiry
Const ASC_ACK:Int = 6       ' Acknowledgment
Const ASC_BEL:Int = 7       ' Bell
Const ASC_BACKSPACE:Int = 8 ' Backspace
Const ASC_TAB:Int = 9       ' Horizontal tab
Const ASC_LF:Int = 10       ' Linefeed
Const ASC_VTAB:Int = 11     ' Vertical tab
Const ASC_FF:Int = 12       ' Form feed
Const ASC_CR:Int = 13       ' Carriage return
Const ASC_SO:Int = 14       ' Shift Out
Const ASC_SI:Int = 15       ' Shift In
Const ASC_DLE:Int = 16      ' Data Line Escape
Const ASC_DC1:Int = 17      ' Device Control 1
Const ASC_DC2:Int = 18      ' Device Control 2
Const ASC_DC3:Int = 19      ' Device Control 3
Const ASC_DC4:Int = 20      ' Device Control 4
Const ASC_NAK:Int = 21      ' Negative Acknowledgment
Const ASC_SYN:Int = 22      ' Synchronous Idle
Const ASC_ETB:Int = 23      ' End of Transmit Block
Const ASC_CAN:Int = 24      ' Cancel
Const ASC_EM:Int = 25       ' End of Medium
Const ASC_SUB:Int = 26      ' Substitute
Const ASC_ESCAPE:Int = 27   ' Escape
Const ASC_FS:Int = 28       ' File separator
Const ASC_GS:Int = 29       ' Group separator
Const ASC_RS:Int = 30       ' Record separator
Const ASC_US:Int = 31       ' Unit separator

' visible characters
Const ASC_SPACE:Int = 32                ' '
Const ASC_EXCLAMATION:Int = 33          '!'
Const ASC_DOUBLE_QUOTE:Int = 34         '"'
Const ASC_HASH:Int = 35                 '#'
Const ASC_DOLLAR:Int = 36               '$'
Const ASC_PERCENT:Int = 37              '%'
Const ASC_AMPERSAND:Int = 38            '&'
Const ASC_SINGLE_QUOTE:Int = 39         '''
Const ASC_OPEN_PARENTHESIS:Int = 40     '('
Const ASC_CLOSE_PARENTHESIS:Int = 41    ')'
Const ASC_ASTERISK:Int = 42             '*'
Const ASC_PLUS:Int = 43                 '+'
Const ASC_COMMA:Int = 44                ','
Const ASC_HYPHEN:Int = 45               '-'
Const ASC_PERIOD:Int = 46               '.'
Const ASC_SLASH:Int = 47                '/'
Const ASC_0:Int = 48
Const ASC_1:Int = 49
Const ASC_2:Int = 50
Const ASC_3:Int = 51
Const ASC_4:Int = 52
Const ASC_5:Int = 53
Const ASC_6:Int = 54
Const ASC_7:Int = 55
Const ASC_8:Int = 56
Const ASC_9:Int = 57
Const ASC_COLON:Int = 58        ':'
Const ASC_SEMICOLON:Int = 59    ';'
Const ASC_LESS_THAN:Int = 60    '<'
Const ASC_EQUALS:Int = 61       '='
Const ASC_GREATER_THAN:Int = 62 '>'
Const ASC_QUESTION:Int = 63     '?'
Const ASC_AT:Int = 64           '@'
Const ASC_UPPER_A:Int = 65
Const ASC_UPPER_B:Int = 66
Const ASC_UPPER_C:Int = 67
Const ASC_UPPER_D:Int = 68
Const ASC_UPPER_E:Int = 69
Const ASC_UPPER_F:Int = 70
Const ASC_UPPER_G:Int = 71
Const ASC_UPPER_H:Int = 72
Const ASC_UPPER_I:Int = 73
Const ASC_UPPER_J:Int = 74
Const ASC_UPPER_K:Int = 75
Const ASC_UPPER_L:Int = 76
Const ASC_UPPER_M:Int = 77
Const ASC_UPPER_N:Int = 78
Const ASC_UPPER_O:Int = 79
Const ASC_UPPER_P:Int = 80
Const ASC_UPPER_Q:Int = 81
Const ASC_UPPER_R:Int = 82
Const ASC_UPPER_S:Int = 83
Const ASC_UPPER_T:Int = 84
Const ASC_UPPER_U:Int = 85
Const ASC_UPPER_V:Int = 86
Const ASC_UPPER_W:Int = 87
Const ASC_UPPER_X:Int = 88
Const ASC_UPPER_Y:Int = 89
Const ASC_UPPER_Z:Int = 90
Const ASC_OPEN_BRACKET:Int = 91     '['
Const ASC_BACKSLASH:Int = 92        '\'
Const ASC_CLOSE_BRACKET:Int = 93    ']'
Const ASC_CIRCUMFLEX:Int = 94       '^'
Const ASC_UNDERSCORE:Int = 95       '_'
Const ASC_BACKTICK:Int = 96         '`'
Const ASC_LOWER_A:Int = 97
Const ASC_LOWER_B:Int = 98
Const ASC_LOWER_C:Int = 99
Const ASC_LOWER_D:Int = 100
Const ASC_LOWER_E:Int = 101
Const ASC_LOWER_F:Int = 102
Const ASC_LOWER_G:Int = 103
Const ASC_LOWER_H:Int = 104
Const ASC_LOWER_I:Int = 105
Const ASC_LOWER_J:Int = 106
Const ASC_LOWER_K:Int = 107
Const ASC_LOWER_L:Int = 108
Const ASC_LOWER_M:Int = 109
Const ASC_LOWER_N:Int = 110
Const ASC_LOWER_O:Int = 111
Const ASC_LOWER_P:Int = 112
Const ASC_LOWER_Q:Int = 113
Const ASC_LOWER_R:Int = 114
Const ASC_LOWER_S:Int = 115
Const ASC_LOWER_T:Int = 116
Const ASC_LOWER_U:Int = 117
Const ASC_LOWER_V:Int = 118
Const ASC_LOWER_W:Int = 119
Const ASC_LOWER_X:Int = 120
Const ASC_LOWER_Y:Int = 121
Const ASC_LOWER_Z:Int = 122
Const ASC_OPEN_BRACE:Int = 123  '{'
Const ASC_PIPE:Int = 124        '|'
Const ASC_CLOSE_BRACE:Int = 125 '}'
Const ASC_TILDE:Int = 126       '~'
Const ASC_DELETE:Int = 127

#if HOST="macos" And TARGET="glfw"
	Import "native/diddy.${TARGET}.mac.${LANG}"
#else
	Import "native/diddy.${TARGET}.${LANG}"
#end
Import mojo
Import assert

Extern

	#If LANG="cpp" Then
		Function RealMillisecs:Int() = "diddy::systemMillisecs"
		Function FlushKeys:Void() = "diddy::flushKeys"
		Function HideMouse:Void() = "diddy::hideMouse"
		Function ShowMouse:Void() = "diddy::showMouse"
		Function GetUpdateRate:Int() = "diddy::getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy::setMouse"
		Function ShowKeyboard:Void() = "diddy::showKeyboard"
		Function LaunchBrowser:Void(address:String) = "diddy::launchBrowser"
		Function LaunchEmail:Void(subject:String, text:String) = "diddy::launchEmail"
	#Else
		Function RealMillisecs:Int() = "diddy.systemMillisecs"
		Function FlushKeys:Void() = "diddy.flushKeys"
		Function HideMouse:Void() = "diddy.hideMouse"
		Function ShowMouse:Void() = "diddy.showMouse"
		Function GetUpdateRate:Int() = "diddy.getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy.setMouse"
		Function ShowKeyboard:Void() = "diddy.showKeyboard"
		Function LaunchBrowser:Void(address:String) = "diddy.launchBrowser"
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "diddy.launchEmail"
	#End
Public

Function ExitApp:Void()
	Error ""
End

Function RectsOverlap:Int(x0:Float, y0:Float, w0:Float, h0:Float, x2:Float, y2:Float, w2:Float, h2:Float)
	If x0 > (x2 + w2) Or (x0 + w0) < x2 Then Return False
	If y0 > (y2 + h2) Or (y0 + h0) < y2 Then Return False
	Return True
End

Function DrawRectOutline:Void(x:Int, y:Int, w:Int, h:Int)
	w -= 1
	h -= 1
	DrawLine(x,y,x+w,y)
	DrawLine(x+w,y,x+w,y+h)
	DrawLine(x+w,y+h,x,y+h)
	DrawLine(x,y+h,x,y)
End

Function LoadBitmap:Image(path$, flags%=0)
	Local pointer:Image = LoadImage(path, 1, flags)

	AssertNotNull(pointer, "Error loading bitmap "+path)

   	Return pointer
End

Function LoadAnimBitmap:Image(path$, w%, h%, count%, tmpImage:Image)
	'tmpImage = loadBitmap(path) <-- This creates another image, decided to just copy the code here
	tmpImage = LoadImage(path)

	AssertNotNull(tmpImage, "Error loading bitmap "+path)

	Local pointer:Image = tmpImage.GrabImage( 0, 0, w, h, count, Image.MidHandle)

   	Return pointer
End

Function LoadSoundSample:Sound(path$)
	Local pointer:Sound = LoadSound(path)
	AssertNotNull(pointer, "Error loading sound "+path)
	Return pointer
End

Function FormatNumeric:String(value:Float)
	Local i:Int,s:String,ns:String,k:Int
	Local os:String
	s=String(value)
	os=s
	Local pos:Int=s.Length()
	If s.Find(".")>0 pos=s.Find(".") Else os=""
	For i=pos To 1 Step -1
		If k>2 ns+="." k=0
		k+=1
		ns=ns+Mid(s,i,1)
	Next
	s=""
	For i= ns.Length() To 1 Step -1
		s+=Mid(ns,i,1)
	Next
	If os<>"" s=s+","+os[pos+1..]
	Return s
End

Function Left$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[..n]
End

Function Right$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[str.Length()-n..]
End

Function LSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=str+rep
	Return str[..n]
End

Function RSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=rep+str
	Return str[str.Length()-n..]
End

Function Mid$( str$,pos:Int,size:Int=-1 )
	If pos>str.Length() Return ""
	pos-=1
	If( size<0 ) Return str[pos..]
	If pos<0 size=size+pos pos=0
	If pos+size>str.Length() size=str.Length()-pos
	Return str[pos..pos+size]
End

Function StripDir$( path$ )
	Local i:=path.FindLast( "/" )
	If i<>-1 Return path[i+1..]
	Return path
End

Function StripExt$( path$ )
	Local i:=path.FindLast( "." )
	If i<>-1 And path.Find( "/",i+1 )=-1 Return path[..i]
	Return path
End

Function StripAll$( path$ )
	Return StripDir( StripExt( path ) )
End

Function Round%(flot#)
	Return Floor(flot+0.5)
End

Function PointInSpot:Int(x1:Float, y1:Float, x2:Float, y2:Float, radius:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return dx * dx + dy * dy <= radius * radius
End

Function AnyInputPressed:Bool()
	For Local i:Int = 0 To 511
		If KeyHit(i) Then Return True
	Next
	Return False
End

Function FormatNumber:String(number:Float, decimal:Int=4, comma:Int=0, padleft:Int=0 )
	Assert(decimal > -1 And comma > -1 And padleft > -1, "Negative numbers not allowed in FormatNumber()")

	Local str:String = number
	Local dl:Int = str.Find(".")
	If decimal = 0 Then decimal = -1
	str = str[..dl+decimal+1]

	If comma
		While dl>comma
			str = str[..dl-comma] + "," + str[dl-comma..]
			dl -= comma
		Wend
	End

	If padleft
		Local paddedLength:Int = padleft+decimal+1
		If paddedLength < str.Length Then str = "Error"
		str = RSet(str,paddedLength)
	End
	Return str
End

Function IsWhitespace:Bool(str:String)
	Return str = "~t" Or str = "~n" Or str = "~r" Or str = " "
End

Function IsWhitespace:Bool(val:Int)
	Return val = ASC_TAB Or val = ASC_LF Or val = ASC_CR Or val = ASC_SPACE
End

' TODO: allow encoding of Int[]
' TODO: enabling lineWrap will wrap the string every lineWrapWidth characters (defaults to 80)
' enabling padOutput will pad the ending of the output with equals characters (=) to next 4-byte boundary
Function EncodeBase64:String(src:String, padOutput:Bool=False, lineWrap:Bool=False, lineWrapWidth:Int=80)
	Local rv:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	If src.Length = 0 Then Return ""
	Repeat
		s1 = src[i]
		If i+1 < src.Length Then s2 = src[i+1] Else s2 = 0
		If i+2 < src.Length Then s3 = src[i+2] Else s3 = 0
		a = s1 Shr 2
		b = ((s1 & 3) Shl 4) | (s2 Shr 4)
		c = ((s2 & 15) Shl 2) | (s3 Shr 6)
		d = s3 & 63
		If i+1 >= src.Length Then c = 64
		If i+2 >= src.Length Then d = 64
		rv += BASE64_CHARS[a..a+1] + BASE64_CHARS[b..b+1]
		If c < 64 Or padOutput Then rv += BASE64_CHARS[c..c+1]
		If d < 64 Or padOutput Then rv += BASE64_CHARS[d..d+1]
		i += 3
	Until i >= src.Length
	Return rv
End

' TODO: convert this to use BASE64_ARRAY
' I'll do this once Monkey has a decent string builder class.
Function DecodeBase64:String(src:String)
	Local rv:String = "", src2:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	' remove any non-base64 characters
	For i = 0 Until src.Length
		If BASE64_CHARS.Find(src[i..i+1]) >= 0 Then src2 += src[i..i+1]
	Next
	If src2.Length = 0 Then Return ""
	i = 0
	Repeat
		a = BASE64_CHARS.Find(src2[i..i+1])
		If i+1 > src2.Length Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_CHARS.Find(src2[i+1..i+2])
		If i+2 < src2.Length Then c = BASE64_CHARS.Find(src2[i+2..i+3]) Else c = 64
		If i+3 < src2.Length Then d = BASE64_CHARS.Find(src2[i+3..i+4]) Else d = 64
		s1 = (a Shl 2) | (b Shr 4)
		s2 = ((b & 15) Shl 4) | (c Shr 2)
		s3 = ((c & 3) Shl 6) | d
		rv += String.FromChar(s1)
		If c <> 64 Then rv += String.FromChar(s2)
		If d <> 64 Then rv += String.FromChar(s3)
		i += 4
	Until i >= src2.Length
	Return rv
End

Function DecodeBase64Bytes:Int[](src:String)
	InitBase64()
	Local a:Int, b:Int, c:Int, d:Int, i:Int, j:Int
	Local src2:Int[] = New Int[src.Length]

	' find out how many base64 characters
	Local srclen:Int = 0
	For i = 0 Until src.Length
		If BASE64_ARRAY[src[i]] >= 0 Then
			src2[srclen] = src[i]
			srclen += 1
		End
	Next

	' die if there are no base64 chars
	If srclen = 0 Then Return []

	' get the target length and create the array
	Local len:Int = 3*srclen/4
	If srclen Mod 4 = 1 Or srclen Mod 4 = 2 Then
		len += 1
	ElseIf srclen Mod 4 = 3 Then
		len += 2
	End
	Local rv:Int[] = New Int[len]

	i = 0
	j = 0
	Repeat
		a = BASE64_ARRAY[src2[i]]
		If i+1 > srclen Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_ARRAY[src2[i+1]]
		If i+2 < srclen Then c = BASE64_ARRAY[src2[i+2]] Else c = 64
		If i+3 < srclen Then d = BASE64_ARRAY[src2[i+3]] Else d = 64
		rv[j] = (a Shl 2) | (b Shr 4)
		If j+1 < len Then rv[j+1] = ((b & 15) Shl 4) | (c Shr 2)
		If j+2 < len Then rv[j+2] = ((c & 3) Shl 6) | d
		i += 4
		j += 3
	Until i >= srclen
	Return rv
End

Function InitBase64:Void()
	If BASE64_ARRAY.Length = 0 Then
		BASE64_ARRAY = New Int[256]
		Local i% = 0
		For i = 0 Until BASE64_ARRAY.Length
			BASE64_ARRAY[i] = -1
		Next
		For i = 0 Until BASE64_CHARS.Length
			BASE64_ARRAY[BASE64_CHARS[i]] = i
		Next
	End
End

' constants

Const BASE64_CHARS:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
Global BASE64_ARRAY:Int[] = []

' control characters
Const ASC_NUL:Int = 0       ' Null character
Const ASC_SOH:Int = 1       ' Start of Heading
Const ASC_STX:Int = 2       ' Start of Text
Const ASC_ETX:Int = 3       ' End of Text
Const ASC_EOT:Int = 4       ' End of Transmission
Const ASC_ENQ:Int = 5       ' Enquiry
Const ASC_ACK:Int = 6       ' Acknowledgment
Const ASC_BEL:Int = 7       ' Bell
Const ASC_BACKSPACE:Int = 8 ' Backspace
Const ASC_TAB:Int = 9       ' Horizontal tab
Const ASC_LF:Int = 10       ' Linefeed
Const ASC_VTAB:Int = 11     ' Vertical tab
Const ASC_FF:Int = 12       ' Form feed
Const ASC_CR:Int = 13       ' Carriage return
Const ASC_SO:Int = 14       ' Shift Out
Const ASC_SI:Int = 15       ' Shift In
Const ASC_DLE:Int = 16      ' Data Line Escape
Const ASC_DC1:Int = 17      ' Device Control 1
Const ASC_DC2:Int = 18      ' Device Control 2
Const ASC_DC3:Int = 19      ' Device Control 3
Const ASC_DC4:Int = 20      ' Device Control 4
Const ASC_NAK:Int = 21      ' Negative Acknowledgment
Const ASC_SYN:Int = 22      ' Synchronous Idle
Const ASC_ETB:Int = 23      ' End of Transmit Block
Const ASC_CAN:Int = 24      ' Cancel
Const ASC_EM:Int = 25       ' End of Medium
Const ASC_SUB:Int = 26      ' Substitute
Const ASC_ESCAPE:Int = 27   ' Escape
Const ASC_FS:Int = 28       ' File separator
Const ASC_GS:Int = 29       ' Group separator
Const ASC_RS:Int = 30       ' Record separator
Const ASC_US:Int = 31       ' Unit separator

' visible characters
Const ASC_SPACE:Int = 32                ' '
Const ASC_EXCLAMATION:Int = 33          '!'
Const ASC_DOUBLE_QUOTE:Int = 34         '"'
Const ASC_HASH:Int = 35                 '#'
Const ASC_DOLLAR:Int = 36               '$'
Const ASC_PERCENT:Int = 37              '%'
Const ASC_AMPERSAND:Int = 38            '&'
Const ASC_SINGLE_QUOTE:Int = 39         '''
Const ASC_OPEN_PARENTHESIS:Int = 40     '('
Const ASC_CLOSE_PARENTHESIS:Int = 41    ')'
Const ASC_ASTERISK:Int = 42             '*'
Const ASC_PLUS:Int = 43                 '+'
Const ASC_COMMA:Int = 44                ','
Const ASC_HYPHEN:Int = 45               '-'
Const ASC_PERIOD:Int = 46               '.'
Const ASC_SLASH:Int = 47                '/'
Const ASC_0:Int = 48
Const ASC_1:Int = 49
Const ASC_2:Int = 50
Const ASC_3:Int = 51
Const ASC_4:Int = 52
Const ASC_5:Int = 53
Const ASC_6:Int = 54
Const ASC_7:Int = 55
Const ASC_8:Int = 56
Const ASC_9:Int = 57
Const ASC_COLON:Int = 58        ':'
Const ASC_SEMICOLON:Int = 59    ';'
Const ASC_LESS_THAN:Int = 60    '<'
Const ASC_EQUALS:Int = 61       '='
Const ASC_GREATER_THAN:Int = 62 '>'
Const ASC_QUESTION:Int = 63     '?'
Const ASC_AT:Int = 64           '@'
Const ASC_UPPER_A:Int = 65
Const ASC_UPPER_B:Int = 66
Const ASC_UPPER_C:Int = 67
Const ASC_UPPER_D:Int = 68
Const ASC_UPPER_E:Int = 69
Const ASC_UPPER_F:Int = 70
Const ASC_UPPER_G:Int = 71
Const ASC_UPPER_H:Int = 72
Const ASC_UPPER_I:Int = 73
Const ASC_UPPER_J:Int = 74
Const ASC_UPPER_K:Int = 75
Const ASC_UPPER_L:Int = 76
Const ASC_UPPER_M:Int = 77
Const ASC_UPPER_N:Int = 78
Const ASC_UPPER_O:Int = 79
Const ASC_UPPER_P:Int = 80
Const ASC_UPPER_Q:Int = 81
Const ASC_UPPER_R:Int = 82
Const ASC_UPPER_S:Int = 83
Const ASC_UPPER_T:Int = 84
Const ASC_UPPER_U:Int = 85
Const ASC_UPPER_V:Int = 86
Const ASC_UPPER_W:Int = 87
Const ASC_UPPER_X:Int = 88
Const ASC_UPPER_Y:Int = 89
Const ASC_UPPER_Z:Int = 90
Const ASC_OPEN_BRACKET:Int = 91     '['
Const ASC_BACKSLASH:Int = 92        '\'
Const ASC_CLOSE_BRACKET:Int = 93    ']'
Const ASC_CIRCUMFLEX:Int = 94       '^'
Const ASC_UNDERSCORE:Int = 95       '_'
Const ASC_BACKTICK:Int = 96         '`'
Const ASC_LOWER_A:Int = 97
Const ASC_LOWER_B:Int = 98
Const ASC_LOWER_C:Int = 99
Const ASC_LOWER_D:Int = 100
Const ASC_LOWER_E:Int = 101
Const ASC_LOWER_F:Int = 102
Const ASC_LOWER_G:Int = 103
Const ASC_LOWER_H:Int = 104
Const ASC_LOWER_I:Int = 105
Const ASC_LOWER_J:Int = 106
Const ASC_LOWER_K:Int = 107
Const ASC_LOWER_L:Int = 108
Const ASC_LOWER_M:Int = 109
Const ASC_LOWER_N:Int = 110
Const ASC_LOWER_O:Int = 111
Const ASC_LOWER_P:Int = 112
Const ASC_LOWER_Q:Int = 113
Const ASC_LOWER_R:Int = 114
Const ASC_LOWER_S:Int = 115
Const ASC_LOWER_T:Int = 116
Const ASC_LOWER_U:Int = 117
Const ASC_LOWER_V:Int = 118
Const ASC_LOWER_W:Int = 119
Const ASC_LOWER_X:Int = 120
Const ASC_LOWER_Y:Int = 121
Const ASC_LOWER_Z:Int = 122
Const ASC_OPEN_BRACE:Int = 123  '{'
Const ASC_PIPE:Int = 124        '|'
Const ASC_CLOSE_BRACE:Int = 125 '}'
Const ASC_TILDE:Int = 126       '~'
Const ASC_DELETE:Int = 127


Fixed a bug where long press and click were both firing.

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

Strict

Import mojo
Import assert
Import functions

Private

Global charForCodeArray:Int[]
Global charForShiftCodeArray:Int[]

' warning: do not try to use these externally! they are here until Mark fixes the official monkey constants in input.monkey
Const VK_ALT:Int = 18
Const VK_SEMICOLON:Int = 59
Const VK_EQUALS:Int = 107
Const VK_HYPHEN:Int = 109
Const VK_BACKSLASH:Int = 220

Public

Const EVENT_KEY_DOWN:Int = 1
Const EVENT_KEY_RELEASED:Int = 2
Const EVENT_KEY_HIT:Int = 3

Class InputCache
Private
	Field keyHitEnumerator:KeyEventEnumerator
	Field keyDownEnumerator:KeyEventEnumerator
	Field keyReleasedEnumerator:KeyEventEnumerator
	Field keyHitWrapper:EnumWrapper<KeyEventEnumerator>
	Field keyDownWrapper:EnumWrapper<KeyEventEnumerator>
	Field keyReleasedWrapper:EnumWrapper<KeyEventEnumerator>

Public
	Const FIRST_KEY:Int = KEY_BACKSPACE
	Const LAST_KEY:Int = KEY_QUOTES

	Const TOUCH_COUNT:Int = 32
	Const MOUSE_COUNT:Int = 3
	Const KEY_COUNT:Int = LAST_KEY-FIRST_KEY+1

	Const INPUT_COUNT:Int = 512

	Const FLING_THRESHOLD:Float = 250
	Const IGNORE_CLICK_DISTANCE:Float = 20
	Const LONG_PRESS_TIME:Int = 1000

	' TouchHit() for 0-31
	Field touchHit:Int[] = New Int[TOUCH_COUNT]
	' The time it was hit
	Field touchHitTime:Int[] = New Int[TOUCH_COUNT]
	' TouchDown() for 0-31
	Field touchDown:Int[] = New Int[TOUCH_COUNT]
	' The time it was first down
	Field touchDownTime:Int[] = New Int[TOUCH_COUNT]
	' TouchDown() is false but was true last loop
	Field touchReleased:Int[] = New Int[TOUCH_COUNT]
	' The time it was released
	Field touchReleasedTime:Int[] = New Int[TOUCH_COUNT]
	' the number of TouchDown() calls that returned true
	Field touchDownCount:Int
	' thte number of TouchHit() calls that returned true
	Field touchHitCount:Int
	' the number of released touches
	Field touchReleasedCount:Int
	' the highest index of TouchDown() that returned true
	Field maxTouchDown:Int = -1
	' the highest index of TouchHit() that returned true
	Field maxTouchHit:Int = -1
	' the highest index of a released touch
	Field maxTouchReleased:Int = -1
	' the lowest index of TouchDown() that returned true
	Field minTouchDown:Int = -1
	' the lowest index of TouchHit() that returned true
	Field minTouchHit:Int = -1
	' the lowest index of a released touch
	Field minTouchReleased:Int = -1
	' TouchX() And TouchY() For 0-31
	Field touchX:Float[] = New Float[TOUCH_COUNT]
	Field touchY:Float[] = New Float[TOUCH_COUNT]
	' should we monitor touch events?
	Field monitorTouch:Bool = False

	' the index of touches that were hit, released, or down (use touchHitCount, touchDownCount, touchReleasedCount]
	Field currentTouchHit:Int[] = New Int[TOUCH_COUNT]
	Field currentTouchDown:Int[] = New Int[TOUCH_COUNT]
	Field currentTouchReleased:Int[] = New Int[TOUCH_COUNT]

	' MouseHit() for 0-2
	Field mouseHit:Int[] = New Int[MOUSE_COUNT]
	' The time it was hit
	Field mouseHitTime:Int[] = New Int[MOUSE_COUNT]
	' MouseDown() for 0-2
	Field mouseDown:Int[] = New Int[MOUSE_COUNT]
	' The time it was first down
	Field mouseDownTime:Int[] = New Int[MOUSE_COUNT]
	' MouseDown() is false but was true last loop
	Field mouseReleased:Int[] = New Int[MOUSE_COUNT]
	' The time it was released
	Field mouseReleasedTime:Int[] = New Int[MOUSE_COUNT]
	' MouseX() And MouseY()
	Field mouseX:Int
	Field mouseY:Int
	' is any mouse button down?
	Field mouseDownCount:Int
	' was any mouse button hit?
	Field mouseHitCount:Int
	' was any mouse button released?
	Field mouseReleasedCount:Int
	' should we monitor mouse events?
	Field monitorMouse:Bool = False

	' the index of mouse buttons that were hit, released, or down (use mouseHitCount, mouseDownCount, mouseReleasedCount]
	Field currentMouseHit:Int[] = New Int[MOUSE_COUNT]
	Field currentMouseDown:Int[] = New Int[MOUSE_COUNT]
	Field currentMouseReleased:Int[] = New Int[MOUSE_COUNT]

	' KeyHit() for each monitored key
	Field keyHit:Int[] = New Int[INPUT_COUNT]
	' The time it was hit
	Field keyHitTime:Int[] = New Int[INPUT_COUNT]
	' KeyDown() for each monitored key
	Field keyDown:Int[] = New Int[INPUT_COUNT]
	' The time it was first down
	Field keyDownTime:Int[] = New Int[INPUT_COUNT]
	' KeyDown() is false but was true last loop
	Field keyReleased:Int[] = New Int[INPUT_COUNT]
	' The time it was released
	Field keyReleasedTime:Int[] = New Int[INPUT_COUNT]
	' is any monitored key down?
	Field keyDownCount:Int
	' was any monitored key hit?
	Field keyHitCount:Int
	' was any monitored key released?
	Field keyReleasedCount:Int
	' If true, we should monitor that key (defaults To false)
	Field monitorKey:Bool[] = New Bool[INPUT_COUNT]
	' the number of keys we are monitoring; this is so we can skip key checks if we aren't monitoring any
	Field monitorKeyCount:Int = 0

	' the index of keys that were hit, released, or down (use keyHitCount, keyDownCount, keyReleasedCount]
	Field currentKeysHit:Int[] = New Int[INPUT_COUNT]
	Field currentKeysDown:Int[] = New Int[INPUT_COUNT]
	Field currentKeysReleased:Int[] = New Int[INPUT_COUNT]

	' for touch handling
	Field touchData:TouchData[] = New TouchData[TOUCH_COUNT]
	Field flingThreshold:Float = FLING_THRESHOLD
	Field longPressTime:Int = LONG_PRESS_TIME

' Properties
	Method KeysHit:EnumWrapper<KeyEventEnumerator>() Property
		Return keyHitWrapper
	End
	Method KeysDown:EnumWrapper<KeyEventEnumerator>() Property
		Return keyDownWrapper
	End
	Method KeysReleased:EnumWrapper<KeyEventEnumerator>() Property
		Return keyReleasedWrapper
	End
	Method FlingThreshold:Float() Property
		Return flingThreshold
	End
	Method FlingThreshold:Void(flingThreshold:Float) Property
		Self.flingThreshold = flingThreshold
	End
	Method LongPressTime:Float() Property
		Return longPressTime
	End
	Method LongPressTime:Void(longPressTime:Float) Property
		Self.longPressTime = longPressTime
	End

' Constructors
	Method New()
		keyHitEnumerator = New KeyEventEnumerator(Self, EVENT_KEY_HIT)
		keyDownEnumerator = New KeyEventEnumerator(Self, EVENT_KEY_DOWN)
		keyReleasedEnumerator = New KeyEventEnumerator(Self, EVENT_KEY_RELEASED)
		keyHitWrapper = New EnumWrapper<KeyEventEnumerator>(keyHitEnumerator)
		keyDownWrapper = New EnumWrapper<KeyEventEnumerator>(keyDownEnumerator)
		keyReleasedWrapper = New EnumWrapper<KeyEventEnumerator>(keyReleasedEnumerator)
		For Local i:Int = 0 Until touchData.Length
			touchData[i] = New TouchData
		Next
		#If TARGET="android" Or TARGET="ios"
			monitorTouch = True
			monitorMouse = False
		#Else
			monitorTouch = False
			monitorMouse = True
		#End
	End

' Public methods
	Method MonitorNothing:Void()
		' clear mouse
		monitorMouse = False
		mouseDownCount = 0
		mouseHitCount = 0
		mouseReleasedCount = 0

		' clear touch
		monitorTouch = False
		touchDownCount = 0
		touchHitCount = 0
		touchReleasedCount = 0
		maxTouchDown = -1
		maxTouchHit = -1
		maxTouchReleased = -1
		minTouchDown = -1
		minTouchHit = -1
		minTouchReleased = -1

		' clear keys
		monitorKeyCount = 0
		For Local i:Int = 0 Until KEY_COUNT
			monitorKey[i] = False
			keyHit[i] = 0
			keyDown[i] = 0
			keyReleased[i] = 0
		Next
	End

	Method MonitorAll:Void(val:Bool=True)
		MonitorMouse(val)
		MonitorTouch(val)
		MonitorAllKeys(val)
	End

	Method MonitorAllKeys:Void(val:Bool=True)
		If val Then
			monitorKeyCount = KEY_COUNT
		Else
			monitorKeyCount = 0
		End
		For Local i:Int = FIRST_KEY To LAST_KEY
			monitorKey[i] = val
			keyHit[i] = 0
			keyDown[i] = 0
			keyReleased[i] = 0
		Next
	End

	Method MonitorKey:Void(keyNum:Int, val:Bool=True)
		If val And Not monitorKey[keyNum] Then
			monitorKeyCount += 1
		ElseIf Not val And monitorKey[keyNum] Then
			monitorKeyCount -= 1
		End
		monitorKey[keyNum] = val
		keyHit[keyNum] = 0
		keyDown[keyNum] = 0
		keyReleased[keyNum] = 0
	End

	Method MonitorTouch:Void(val:Bool=True)
		monitorTouch = val
		touchDownCount = 0
		touchHitCount = 0
		touchReleasedCount = 0
		maxTouchDown = -1
		maxTouchHit = -1
		maxTouchReleased = -1
		minTouchDown = -1
		minTouchHit = -1
		minTouchReleased = -1
	End

	Method MonitorMouse:Void(val:Bool=True)
		monitorMouse = val
		mouseDownCount = 0
		mouseHitCount = 0
		mouseReleasedCount = 0
	End

	Method MonitorControlKeys:Void(val:Bool=True)
		MonitorKey(KEY_CONTROL, val)
		MonitorKey(KEY_SHIFT, val)
		MonitorKey(VK_ALT, val)
	End

	Method ReadInput:Void()
		Local newval:Int = 0
		Local now:Int = Millisecs()

		' get the touch events
		If monitorTouch Then
			touchDownCount = 0
			touchHitCount = 0
			touchReleasedCount = 0
			maxTouchDown = -1
			maxTouchHit = -1
			maxTouchReleased = -1
			minTouchDown = -1
			minTouchHit = -1
			minTouchReleased = -1
			For Local i:Int = 0 Until TOUCH_COUNT
				' get the touch hit
				newval = TouchHit(i)
				If Not touchHit[i] And newval Then
					touchHitTime[i] = now
				End
				touchHit[i] = newval
				' get the touch down
				newval = TouchDown(i)
				If newval And Not touchDown[i] Then
					touchDownTime[i] = now
				End
				' get the touch released
				If touchDown[i] And Not newval Then
					touchReleasedTime[i] = now
					touchReleased[i] = True
				Else
					touchReleased[i] = False
				End
				touchDown[i] = newval
				' get the location of each touch
				touchX[i] = TouchX(i)
				touchY[i] = TouchY(i)
				' update the "current" arrays and the min/max indices
				If touchDown[i] Then
					currentTouchDown[touchDownCount] = i
					touchDownCount += 1
					If minTouchDown < 0 Then minTouchDown = i
					maxTouchDown = i
				End
				If touchHit[i] Then
					currentTouchHit[touchHitCount] = i
					touchHitCount += 1
					If minTouchHit < 0 Then minTouchHit = i
					maxTouchHit = i
				End
				If touchReleased[i] Then
					currentTouchReleased[touchReleasedCount] = i
					touchReleasedCount += 1
					If minTouchReleased < 0 Then minTouchReleased = i
					maxTouchReleased = i
				End
			Next
		End

		' get the mouse events
		If monitorMouse Then
			mouseDownCount = 0
			mouseHitCount = 0
			mouseReleasedCount = 0
			For Local i:Int = 0 Until MOUSE_COUNT
				' get the mouse hit
				newval = MouseHit(i)
				If Not mouseHit[i] And newval Then
					mouseHitTime[i] = now
				End
				mouseHit[i] = newval
				' get the mouse down
				newval = MouseDown(i)
				If newval And Not mouseDown[i] Then
					mouseDownTime[i] = now
				End
				' get the mouse released
				If mouseDown[i] And Not newval Then
					mouseReleasedTime[i] = now
					mouseReleased[i] = True
				Else
					mouseReleased[i] = False
				End
				mouseDown[i] = newval
				' update the "current" arrays
				If mouseDown[i] Then
					currentMouseDown[mouseDownCount] = i
					mouseDownCount += 1
				End
				If mouseHit[i] Then
					currentMouseHit[mouseHitCount] = i
					mouseHitCount += 1
				End
				If mouseReleased[i] Then
					currentMouseReleased[mouseReleasedCount] = i
					mouseReleasedCount += 1
				End
			Next
		End

		' get the key events for those we are monitoring
		keyDownCount = 0
		keyHitCount = 0
		keyReleasedCount = 0
		If monitorKeyCount > 0 Then
			For Local i:Int = FIRST_KEY To LAST_KEY
				If monitorKey[i] Then
					' get the key hit
					newval = KeyHit(i)
					If Not keyHit[i] And newval Then
						keyHitTime[i] = now
					End
					keyHit[i] = newval
					' get the key down
					newval = KeyDown(i)
					If newval And Not keyDown[i] Then
						keyDownTime[i] = now
					End
					' get key released
					If keyDown[i] And Not newval Then
						keyReleasedTime[i] = now
						keyReleased[i] = True
					Else
						keyReleased[i] = False
					End
					keyDown[i] = newval
					' update the "current" arrays
					If keyDown[i] Then
						currentKeysDown[keyDownCount] = i
						keyDownCount += 1
					End
					If keyHit[i] Then
						currentKeysHit[keyHitCount] = i
						keyHitCount += 1
					End
					If keyReleased[i] Then
						currentKeysReleased[keyReleasedCount] = i
						keyReleasedCount += 1
					End
				End
			Next
		End
	End

	Method HandleEvents:Void(screen:Screen)
		' handle calling touch hit
		For Local i:Int = 0 Until touchHitCount
			Local pointer:Int = currentTouchHit[i]
			Local x:Int = Int(touchX[pointer])
			Local y:Int = Int(touchY[pointer])
			touchData[pointer].Reset(x, y)
			screen.OnTouchHit(x, y, pointer)
		Next

		' handle calling touch click/released/fling
		For Local i:Int = 0 Until touchReleasedCount
			Local pointer:Int = currentTouchReleased[i]
			Local x:Int = Int(touchX[pointer])
			Local y:Int = Int(touchY[pointer])
			touchData[pointer].Update(x, y);
			If Not touchData[pointer].movedTooFar And Not touchData[pointer].firedLongPress Then
				screen.OnTouchClick(x, y, pointer)
			Else
				' check to see how fast we were moving to see if we fire a fling
				If touchData[pointer].touchVelocityX * touchData[pointer].touchVelocityX +
						touchData[pointer].touchVelocityY * touchData[pointer].touchVelocityY >=
						flingThreshold * flingThreshold Then
					screen.OnTouchFling(x, y, touchData[pointer].touchVelocityX, touchData[pointer].touchVelocityY,
					touchData[pointer].touchVelocitySpeed, pointer)
				End
			End
			screen.OnTouchReleased(x, y, pointer)
		Next

		For Local i:Int = 0 Until touchDownCount
			Local pointer:Int = currentTouchDown[i]
			Local x:Int = Int(touchX[pointer])
			Local y:Int = Int(touchY[pointer])
			touchData[pointer].Update(x, y)
			screen.OnTouchDragged(x, y, touchData[pointer].distanceMovedX, touchData[pointer].distanceMovedY, pointer)
			' check long press
			If Not touchData[pointer].testedLongPress And dt.currentticks-touchData[pointer].firstTouchTime >= longPressTime Then
				touchData[pointer].testedLongPress = True
				If Not touchData[pointer].movedTooFar Then
					' fire long press
					screen.OnTouchLongPress(x, y, pointer)
					touchData[pointer].firedLongPress = True
				End
			End
		Next
	End
End

' top level class for input events
Class InputEvent
Private
	Field eventType:Int
	Field eventTime:Int
	Field shiftDown:Bool
	Field ctrlDown:Bool
	Field altDown:Bool

Public
	Method EventType:Int() Property
		Return eventType
	End
	Method EventTime:Int() Property
		Return eventTime
	End
	Method ShiftDown:Bool() Property
		Return shiftDown
	End
	Method CtrlDown:Bool() Property
		Return ctrlDown
	End
	Method AltDown:Bool() Property
		Return altDown
	End

	Method New(eventType:Int)
		Self.eventType = eventType
	End
End

' subclass of InputEvent to store the key code
Class KeyEvent Extends InputEvent
Private
	Field keyCode:Int
	Field keyChar:Int

Public
	Method KeyCode:Int() Property
		Return keyCode
	End
	Method KeyChar:Int() Property
		Return keyChar
	End

	Method New(eventType:Int)
		Super.New(eventType)
	End
End

Private

Class InputEventEnumerator
Private
	Field ic:InputCache
	Field index:Int
	Field eventType:Int

Public
	Method New(ic:InputCache, eventType:Int)
		Self.ic = ic
		Self.eventType = eventType
	End

	Method Reset:Void()
		index = 0
	End
End

' enumerator class to handle KeyEvents
Class KeyEventEnumerator Extends InputEventEnumerator
Private
	Field event:KeyEvent

Public
	Method New(ic:InputCache, eventType:Int)
		Super.New(ic, eventType)
		Self.event = New KeyEvent
	End

	Method HasNext:Bool()
		If eventType = EVENT_KEY_DOWN Then Return index < ic.keyDownCount
		If eventType = EVENT_KEY_RELEASED Then Return index < ic.keyReleasedCount
		If eventType = EVENT_KEY_HIT Then Return index < ic.keyHitCount
		Return False
	End

	Method NextObject:KeyEvent()
		Assert(HasNext())
		Local idx:Int = 0
		If eventType = EVENT_KEY_DOWN Then
			idx = ic.currentKeysDown[index]
			event.eventTime = ic.keyDownTime[idx]
		ElseIf eventType = EVENT_KEY_RELEASED Then
			idx = ic.currentKeysReleased[index]
			event.eventTime = ic.keyReleasedTime[idx]
		ElseIf eventType = EVENT_KEY_HIT Then
			idx = ic.currentKeysHit[index]
			event.eventTime = ic.keyHitTime[idx]
		End
		index += 1
		event.shiftDown = ic.keyDown[KEY_SHIFT]<>0
		event.ctrlDown = ic.keyDown[KEY_CONTROL]<>0
		event.altDown = ic.keyDown[VK_ALT]<>0
		event.keyCode = idx
		event.keyChar = CharForCode(idx, event.shiftDown)
		Return event
	End
End

' i'd love to use <T Extends InputEventEnumerator> but monkey doesn't support it
Class EnumWrapper<T>
Private
	Field wrappedEnum:T

Public
	Method New(wrappedEnum:T)
		Self.wrappedEnum = wrappedEnum
	End

	Method ObjectEnumerator:T()
		If InputEventEnumerator(wrappedEnum) <> Null Then InputEventEnumerator(wrappedEnum).Reset()
		Return wrappedEnum
	End
End

Function CharForCode:Int(code:Int, shiftDown:Bool)
	BuildCharForCodeArray()
	If shiftDown Then
		Return charForShiftCodeArray[code]
	Else
		Return charForCodeArray[code]
	End
End

Private

Class TouchData
	Const FLING_SAMPLE_RATE:Int = 10

	Field firstTouchX:Int
	Field firstTouchY:Int
	Field firstTouchTime:Int
	Field lastTouchX:Int
	Field lastTouchY:Int
	Field flingSamplesX:Int[FLING_SAMPLE_RATE]
	Field flingSamplesY:Int[FLING_SAMPLE_RATE]
	Field flingSamplesTime:Int[FLING_SAMPLE_RATE]
	Field flingSampleCount:Int
	Field flingSampleNext:Int

	' calculated values
	Field movedTooFar:Bool
	Field testedLongPress:Bool
	Field firedLongPress:Bool
	Field distanceMovedX:Int
	Field distanceMovedY:Int
	Field touchVelocityX:Float
	Field touchVelocityY:Float
	Field touchVelocitySpeed:Float

	Method Reset:Void(x:Int, y:Int)
		firstTouchX = x
		firstTouchY = y
		lastTouchX = x
		lastTouchY = y
		firstTouchTime = Int(dt.currentticks)
		testedLongPress = False
		firedLongPress = False
		For Local i:Int = 0 Until FLING_SAMPLE_RATE
			flingSamplesX[i] = 0
			flingSamplesY[i] = 0
			flingSamplesTime[i] = 0
		Next
		flingSampleCount = 0
		flingSampleNext = 0
		movedTooFar = False
		touchVelocityX = 0
		touchVelocityY = 0
		touchVelocitySpeed = 0
		AddFlingSample(x, y)
	End

	Method AddFlingSample:Void(x:Int, y:Int)
		flingSamplesX[flingSampleNext] = x
		flingSamplesY[flingSampleNext] = y
		flingSamplesTime[flingSampleNext] = Int(dt.currentticks)
		If flingSampleCount < FLING_SAMPLE_RATE Then flingSampleCount += 1
		flingSampleNext += 1
		If flingSampleNext >= FLING_SAMPLE_RATE Then flingSampleNext = 0

		' find the first and last samples
		Local first:Int = flingSampleNext - flingSampleCount
		Local last:Int = flingSampleNext - 1
		While first < 0
			first += FLING_SAMPLE_RATE
		End
		While last < 0
			last += FLING_SAMPLE_RATE
		End

		' get the total delta
		If flingSampleCount > 0 Then
			' calculate the velocity in pixels per second
			Local secs:Float = Float(flingSamplesTime[last] - flingSamplesTime[first]) / 1000
			touchVelocityX = (flingSamplesX[last] - flingSamplesX[first]) / secs
			touchVelocityY = (flingSamplesY[last] - flingSamplesY[first]) / secs
			touchVelocitySpeed = Sqrt(touchVelocityX * touchVelocityX + touchVelocityY * touchVelocityY)
		End
	End

	Method Update:Void(x:Int, y:Int)
		' update distance
		distanceMovedX = x - lastTouchX
		distanceMovedY = y - lastTouchY
		lastTouchX = x
		lastTouchY = y
		' update the fling
		AddFlingSample(x, y)
		' check to see if we moved far enough away that we won't count this as a click
		If Not movedTooFar Then
			' get the first delta
			Local dx:Int = x - firstTouchX
			Local dy:Int = y - firstTouchY
			If dx * dx + dy * dy > InputCache.IGNORE_CLICK_DISTANCE * InputCache.IGNORE_CLICK_DISTANCE Then
				movedTooFar = True
			End
		End
	End
End

' Builds up arrays to convert key codes to characters.
' This is based off a US keyboard layout (sorry!)
' I may look at doing something native to correctly match keyboards to locale.
' warning: 107 seems to be the code for both the EQUALS in the number row and the PLUS on the number pad... wtf?
Function BuildCharForCodeArray:Void()
	If charForCodeArray.Length > 0 Then Return
	charForCodeArray = New Int[InputCache.INPUT_COUNT]
	charForShiftCodeArray = New Int[InputCache.INPUT_COUNT]

	' letters
	For Local i:Int = KEY_A To KEY_Z
		charForCodeArray[i] = i+32
		charForShiftCodeArray[i] = i
	Next

	' numbers
	For Local i:Int = KEY_0 To KEY_9
		charForCodeArray[i] = i
	Next

	' keypad numbers
	For Local i:Int = 96 To 105
		charForCodeArray[i] = KEY_0 + i - 96
	Next

	' shift-numbers
	charForShiftCodeArray[KEY_0] = ASC_CLOSE_PARENTHESIS
	charForShiftCodeArray[KEY_1] = ASC_EXCLAMATION
	charForShiftCodeArray[KEY_2] = ASC_AT
	charForShiftCodeArray[KEY_3] = ASC_HASH
	charForShiftCodeArray[KEY_4] = ASC_DOLLAR
	charForShiftCodeArray[KEY_5] = ASC_PERCENT
	charForShiftCodeArray[KEY_6] = ASC_CIRCUMFLEX
	charForShiftCodeArray[KEY_7] = ASC_AMPERSAND
	charForShiftCodeArray[KEY_8] = ASC_ASTERISK
	charForShiftCodeArray[KEY_9] = ASC_OPEN_PARENTHESIS

	charForCodeArray[VK_SEMICOLON] = ASC_SEMICOLON
	charForCodeArray[VK_EQUALS] = ASC_EQUALS
	charForCodeArray[KEY_COMMA] = ASC_COMMA
	charForCodeArray[VK_HYPHEN] = ASC_HYPHEN
	charForCodeArray[KEY_PERIOD] = ASC_PERIOD
	charForCodeArray[KEY_SLASH] = ASC_SLASH
	charForCodeArray[KEY_TILDE] = ASC_BACKTICK
	charForCodeArray[KEY_OPENBRACKET] = ASC_OPEN_BRACKET
	charForCodeArray[KEY_CLOSEBRACKET] = ASC_CLOSE_BRACKET
	charForCodeArray[KEY_QUOTES] = ASC_DOUBLE_QUOTE
	charForCodeArray[VK_BACKSLASH] = ASC_BACKSLASH
	charForCodeArray[KEY_SPACE] = ASC_SPACE

	charForShiftCodeArray[VK_SEMICOLON] = ASC_COLON
	charForShiftCodeArray[VK_EQUALS] = ASC_PLUS
	charForShiftCodeArray[KEY_COMMA] = ASC_LESS_THAN
	charForShiftCodeArray[VK_HYPHEN] = ASC_UNDERSCORE
	charForShiftCodeArray[KEY_PERIOD] = ASC_GREATER_THAN
	charForShiftCodeArray[KEY_SLASH] = ASC_QUESTION
	charForShiftCodeArray[KEY_TILDE] = ASC_TILDE
	charForShiftCodeArray[KEY_OPENBRACKET] = ASC_OPEN_BRACE
	charForShiftCodeArray[KEY_CLOSEBRACKET] = ASC_CLOSE_BRACE
	charForShiftCodeArray[KEY_QUOTES] = ASC_SINGLE_QUOTE
	charForShiftCodeArray[VK_BACKSLASH] = ASC_PIPE
	charForShiftCodeArray[KEY_SPACE] = ASC_SPACE
End

Strict

Import mojo
Import assert
Import functions

Private

Global charForCodeArray:Int[]
Global charForShiftCodeArray:Int[]

' warning: do not try to use these externally! they are here until Mark fixes the official monkey constants in input.monkey
Const VK_ALT:Int = 18
Const VK_SEMICOLON:Int = 59
Const VK_EQUALS:Int = 107
Const VK_HYPHEN:Int = 109
Const VK_BACKSLASH:Int = 220

Public

Const EVENT_KEY_DOWN:Int = 1
Const EVENT_KEY_RELEASED:Int = 2
Const EVENT_KEY_HIT:Int = 3

Class InputCache
Private
	Field keyHitEnumerator:KeyEventEnumerator
	Field keyDownEnumerator:KeyEventEnumerator
	Field keyReleasedEnumerator:KeyEventEnumerator
	Field keyHitWrapper:EnumWrapper<KeyEventEnumerator>
	Field keyDownWrapper:EnumWrapper<KeyEventEnumerator>
	Field keyReleasedWrapper:EnumWrapper<KeyEventEnumerator>

Public
	Const FIRST_KEY:Int = KEY_BACKSPACE
	Const LAST_KEY:Int = KEY_QUOTES

	Const TOUCH_COUNT:Int = 32
	Const MOUSE_COUNT:Int = 3
	Const KEY_COUNT:Int = LAST_KEY-FIRST_KEY+1

	Const INPUT_COUNT:Int = 512

	Const FLING_THRESHOLD:Float = 250
	Const IGNORE_CLICK_DISTANCE:Float = 20
	Const LONG_PRESS_TIME:Int = 1000

	' TouchHit() for 0-31
	Field touchHit:Int[] = New Int[TOUCH_COUNT]
	' The time it was hit
	Field touchHitTime:Int[] = New Int[TOUCH_COUNT]
	' TouchDown() for 0-31
	Field touchDown:Int[] = New Int[TOUCH_COUNT]
	' The time it was first down
	Field touchDownTime:Int[] = New Int[TOUCH_COUNT]
	' TouchDown() is false but was true last loop
	Field touchReleased:Int[] = New Int[TOUCH_COUNT]
	' The time it was released
	Field touchReleasedTime:Int[] = New Int[TOUCH_COUNT]
	' the number of TouchDown() calls that returned true
	Field touchDownCount:Int
	' thte number of TouchHit() calls that returned true
	Field touchHitCount:Int
	' the number of released touches
	Field touchReleasedCount:Int
	' the highest index of TouchDown() that returned true
	Field maxTouchDown:Int = -1
	' the highest index of TouchHit() that returned true
	Field maxTouchHit:Int = -1
	' the highest index of a released touch
	Field maxTouchReleased:Int = -1
	' the lowest index of TouchDown() that returned true
	Field minTouchDown:Int = -1
	' the lowest index of TouchHit() that returned true
	Field minTouchHit:Int = -1
	' the lowest index of a released touch
	Field minTouchReleased:Int = -1
	' TouchX() And TouchY() For 0-31
	Field touchX:Float[] = New Float[TOUCH_COUNT]
	Field touchY:Float[] = New Float[TOUCH_COUNT]
	' should we monitor touch events?
	Field monitorTouch:Bool = False

	' the index of touches that were hit, released, or down (use touchHitCount, touchDownCount, touchReleasedCount]
	Field currentTouchHit:Int[] = New Int[TOUCH_COUNT]
	Field currentTouchDown:Int[] = New Int[TOUCH_COUNT]
	Field currentTouchReleased:Int[] = New Int[TOUCH_COUNT]

	' MouseHit() for 0-2
	Field mouseHit:Int[] = New Int[MOUSE_COUNT]
	' The time it was hit
	Field mouseHitTime:Int[] = New Int[MOUSE_COUNT]
	' MouseDown() for 0-2
	Field mouseDown:Int[] = New Int[MOUSE_COUNT]
	' The time it was first down
	Field mouseDownTime:Int[] = New Int[MOUSE_COUNT]
	' MouseDown() is false but was true last loop
	Field mouseReleased:Int[] = New Int[MOUSE_COUNT]
	' The time it was released
	Field mouseReleasedTime:Int[] = New Int[MOUSE_COUNT]
	' MouseX() And MouseY()
	Field mouseX:Int
	Field mouseY:Int
	' is any mouse button down?
	Field mouseDownCount:Int
	' was any mouse button hit?
	Field mouseHitCount:Int
	' was any mouse button released?
	Field mouseReleasedCount:Int
	' should we monitor mouse events?
	Field monitorMouse:Bool = False

	' the index of mouse buttons that were hit, released, or down (use mouseHitCount, mouseDownCount, mouseReleasedCount]
	Field currentMouseHit:Int[] = New Int[MOUSE_COUNT]
	Field currentMouseDown:Int[] = New Int[MOUSE_COUNT]
	Field currentMouseReleased:Int[] = New Int[MOUSE_COUNT]

	' KeyHit() for each monitored key
	Field keyHit:Int[] = New Int[INPUT_COUNT]
	' The time it was hit
	Field keyHitTime:Int[] = New Int[INPUT_COUNT]
	' KeyDown() for each monitored key
	Field keyDown:Int[] = New Int[INPUT_COUNT]
	' The time it was first down
	Field keyDownTime:Int[] = New Int[INPUT_COUNT]
	' KeyDown() is false but was true last loop
	Field keyReleased:Int[] = New Int[INPUT_COUNT]
	' The time it was released
	Field keyReleasedTime:Int[] = New Int[INPUT_COUNT]
	' is any monitored key down?
	Field keyDownCount:Int
	' was any monitored key hit?
	Field keyHitCount:Int
	' was any monitored key released?
	Field keyReleasedCount:Int
	' If true, we should monitor that key (defaults To false)
	Field monitorKey:Bool[] = New Bool[INPUT_COUNT]
	' the number of keys we are monitoring; this is so we can skip key checks if we aren't monitoring any
	Field monitorKeyCount:Int = 0

	' the index of keys that were hit, released, or down (use keyHitCount, keyDownCount, keyReleasedCount]
	Field currentKeysHit:Int[] = New Int[INPUT_COUNT]
	Field currentKeysDown:Int[] = New Int[INPUT_COUNT]
	Field currentKeysReleased:Int[] = New Int[INPUT_COUNT]

	' for touch handling
	Field touchData:TouchData[] = New TouchData[TOUCH_COUNT]
	Field flingThreshold:Float = FLING_THRESHOLD
	Field longPressTime:Int = LONG_PRESS_TIME

' Properties
	Method KeysHit:EnumWrapper<KeyEventEnumerator>() Property
		Return keyHitWrapper
	End
	Method KeysDown:EnumWrapper<KeyEventEnumerator>() Property
		Return keyDownWrapper
	End
	Method KeysReleased:EnumWrapper<KeyEventEnumerator>() Property
		Return keyReleasedWrapper
	End
	Method FlingThreshold:Float() Property
		Return flingThreshold
	End
	Method FlingThreshold:Void(flingThreshold:Float) Property
		Self.flingThreshold = flingThreshold
	End
	Method LongPressTime:Float() Property
		Return longPressTime
	End
	Method LongPressTime:Void(longPressTime:Float) Property
		Self.longPressTime = longPressTime
	End

' Constructors
	Method New()
		keyHitEnumerator = New KeyEventEnumerator(Self, EVENT_KEY_HIT)
		keyDownEnumerator = New KeyEventEnumerator(Self, EVENT_KEY_DOWN)
		keyReleasedEnumerator = New KeyEventEnumerator(Self, EVENT_KEY_RELEASED)
		keyHitWrapper = New EnumWrapper<KeyEventEnumerator>(keyHitEnumerator)
		keyDownWrapper = New EnumWrapper<KeyEventEnumerator>(keyDownEnumerator)
		keyReleasedWrapper = New EnumWrapper<KeyEventEnumerator>(keyReleasedEnumerator)
		For Local i:Int = 0 Until touchData.Length
			touchData[i] = New TouchData
		Next
		#If TARGET="android" Or TARGET="ios"
			monitorTouch = True
			monitorMouse = False
		#Else
			monitorTouch = False
			monitorMouse = True
		#End
	End

' Public methods
	Method MonitorNothing:Void()
		' clear mouse
		monitorMouse = False
		mouseDownCount = 0
		mouseHitCount = 0
		mouseReleasedCount = 0

		' clear touch
		monitorTouch = False
		touchDownCount = 0
		touchHitCount = 0
		touchReleasedCount = 0
		maxTouchDown = -1
		maxTouchHit = -1
		maxTouchReleased = -1
		minTouchDown = -1
		minTouchHit = -1
		minTouchReleased = -1

		' clear keys
		monitorKeyCount = 0
		For Local i:Int = 0 Until KEY_COUNT
			monitorKey[i] = False
			keyHit[i] = 0
			keyDown[i] = 0
			keyReleased[i] = 0
		Next
	End

	Method MonitorAll:Void(val:Bool=True)
		MonitorMouse(val)
		MonitorTouch(val)
		MonitorAllKeys(val)
	End

	Method MonitorAllKeys:Void(val:Bool=True)
		If val Then
			monitorKeyCount = KEY_COUNT
		Else
			monitorKeyCount = 0
		End
		For Local i:Int = FIRST_KEY To LAST_KEY
			monitorKey[i] = val
			keyHit[i] = 0
			keyDown[i] = 0
			keyReleased[i] = 0
		Next
	End

	Method MonitorKey:Void(keyNum:Int, val:Bool=True)
		If val And Not monitorKey[keyNum] Then
			monitorKeyCount += 1
		ElseIf Not val And monitorKey[keyNum] Then
			monitorKeyCount -= 1
		End
		monitorKey[keyNum] = val
		keyHit[keyNum] = 0
		keyDown[keyNum] = 0
		keyReleased[keyNum] = 0
	End

	Method MonitorTouch:Void(val:Bool=True)
		monitorTouch = val
		touchDownCount = 0
		touchHitCount = 0
		touchReleasedCount = 0
		maxTouchDown = -1
		maxTouchHit = -1
		maxTouchReleased = -1
		minTouchDown = -1
		minTouchHit = -1
		minTouchReleased = -1
	End

	Method MonitorMouse:Void(val:Bool=True)
		monitorMouse = val
		mouseDownCount = 0
		mouseHitCount = 0
		mouseReleasedCount = 0
	End

	Method MonitorControlKeys:Void(val:Bool=True)
		MonitorKey(KEY_CONTROL, val)
		MonitorKey(KEY_SHIFT, val)
		MonitorKey(VK_ALT, val)
	End

	Method ReadInput:Void()
		Local newval:Int = 0
		Local now:Int = Millisecs()

		' get the touch events
		If monitorTouch Then
			touchDownCount = 0
			touchHitCount = 0
			touchReleasedCount = 0
			maxTouchDown = -1
			maxTouchHit = -1
			maxTouchReleased = -1
			minTouchDown = -1
			minTouchHit = -1
			minTouchReleased = -1
			For Local i:Int = 0 Until TOUCH_COUNT
				' get the touch hit
				newval = TouchHit(i)
				If Not touchHit[i] And newval Then
					touchHitTime[i] = now
				End
				touchHit[i] = newval
				' get the touch down
				newval = TouchDown(i)
				If newval And Not touchDown[i] Then
					touchDownTime[i] = now
				End
				' get the touch released
				If touchDown[i] And Not newval Then
					touchReleasedTime[i] = now
					touchReleased[i] = True
				Else
					touchReleased[i] = False
				End
				touchDown[i] = newval
				' get the location of each touch
				touchX[i] = TouchX(i)
				touchY[i] = TouchY(i)
				' update the "current" arrays and the min/max indices
				If touchDown[i] Then
					currentTouchDown[touchDownCount] = i
					touchDownCount += 1
					If minTouchDown < 0 Then minTouchDown = i
					maxTouchDown = i
				End
				If touchHit[i] Then
					currentTouchHit[touchHitCount] = i
					touchHitCount += 1
					If minTouchHit < 0 Then minTouchHit = i
					maxTouchHit = i
				End
				If touchReleased[i] Then
					currentTouchReleased[touchReleasedCount] = i
					touchReleasedCount += 1
					If minTouchReleased < 0 Then minTouchReleased = i
					maxTouchReleased = i
				End
			Next
		End

		' get the mouse events
		If monitorMouse Then
			mouseDownCount = 0
			mouseHitCount = 0
			mouseReleasedCount = 0
			For Local i:Int = 0 Until MOUSE_COUNT
				' get the mouse hit
				newval = MouseHit(i)
				If Not mouseHit[i] And newval Then
					mouseHitTime[i] = now
				End
				mouseHit[i] = newval
				' get the mouse down
				newval = MouseDown(i)
				If newval And Not mouseDown[i] Then
					mouseDownTime[i] = now
				End
				' get the mouse released
				If mouseDown[i] And Not newval Then
					mouseReleasedTime[i] = now
					mouseReleased[i] = True
				Else
					mouseReleased[i] = False
				End
				mouseDown[i] = newval
				' update the "current" arrays
				If mouseDown[i] Then
					currentMouseDown[mouseDownCount] = i
					mouseDownCount += 1
				End
				If mouseHit[i] Then
					currentMouseHit[mouseHitCount] = i
					mouseHitCount += 1
				End
				If mouseReleased[i] Then
					currentMouseReleased[mouseReleasedCount] = i
					mouseReleasedCount += 1
				End
			Next
		End

		' get the key events for those we are monitoring
		keyDownCount = 0
		keyHitCount = 0
		keyReleasedCount = 0
		If monitorKeyCount > 0 Then
			For Local i:Int = FIRST_KEY To LAST_KEY
				If monitorKey[i] Then
					' get the key hit
					newval = KeyHit(i)
					If Not keyHit[i] And newval Then
						keyHitTime[i] = now
					End
					keyHit[i] = newval
					' get the key down
					newval = KeyDown(i)
					If newval And Not keyDown[i] Then
						keyDownTime[i] = now
					End
					' get key released
					If keyDown[i] And Not newval Then
						keyReleasedTime[i] = now
						keyReleased[i] = True
					Else
						keyReleased[i] = False
					End
					keyDown[i] = newval
					' update the "current" arrays
					If keyDown[i] Then
						currentKeysDown[keyDownCount] = i
						keyDownCount += 1
					End
					If keyHit[i] Then
						currentKeysHit[keyHitCount] = i
						keyHitCount += 1
					End
					If keyReleased[i] Then
						currentKeysReleased[keyReleasedCount] = i
						keyReleasedCount += 1
					End
				End
			Next
		End
	End

	Method HandleEvents:Void(screen:Screen)
		' handle calling touch hit
		For Local i:Int = 0 Until touchHitCount
			Local pointer:Int = currentTouchHit[i]
			Local x:Int = Int(touchX[pointer])
			Local y:Int = Int(touchY[pointer])
			touchData[pointer].Reset(x, y)
			screen.OnTouchHit(x, y, pointer)
		Next

		' handle calling touch click/released/fling
		For Local i:Int = 0 Until touchReleasedCount
			Local pointer:Int = currentTouchReleased[i]
			Local x:Int = Int(touchX[pointer])
			Local y:Int = Int(touchY[pointer])
			touchData[pointer].Update(x, y);
			If Not touchData[pointer].movedTooFar Then
				screen.OnTouchClick(x, y, pointer)
			Else
				' check to see how fast we were moving to see if we fire a fling
				If touchData[pointer].touchVelocityX * touchData[pointer].touchVelocityX +
						touchData[pointer].touchVelocityY * touchData[pointer].touchVelocityY >=
						flingThreshold * flingThreshold Then
					screen.OnTouchFling(x, y, touchData[pointer].touchVelocityX, touchData[pointer].touchVelocityY,
					touchData[pointer].touchVelocitySpeed, pointer)
				End
			End
			screen.OnTouchReleased(x, y, pointer)
		Next

		For Local i:Int = 0 Until touchDownCount
			Local pointer:Int = currentTouchDown[i]
			Local x:Int = Int(touchX[pointer])
			Local y:Int = Int(touchY[pointer])
			touchData[pointer].Update(x, y)
			screen.OnTouchDragged(x, y, touchData[pointer].distanceMovedX, touchData[pointer].distanceMovedY, pointer)
			' check long press
			If Not touchData[pointer].testedLongPress And dt.currentticks-touchData[pointer].firstTouchTime >= longPressTime Then
				touchData[pointer].testedLongPress = True
				If Not touchData[pointer].movedTooFar Then
					' fire long press
					screen.OnTouchLongPress(x, y, pointer)
					touchData[pointer].firedLongPress = True
				End
			End
		Next
	End
End

' top level class for input events
Class InputEvent
Private
	Field eventType:Int
	Field eventTime:Int
	Field shiftDown:Bool
	Field ctrlDown:Bool
	Field altDown:Bool

Public
	Method EventType:Int() Property
		Return eventType
	End
	Method EventTime:Int() Property
		Return eventTime
	End
	Method ShiftDown:Bool() Property
		Return shiftDown
	End
	Method CtrlDown:Bool() Property
		Return ctrlDown
	End
	Method AltDown:Bool() Property
		Return altDown
	End

	Method New(eventType:Int)
		Self.eventType = eventType
	End
End

' subclass of InputEvent to store the key code
Class KeyEvent Extends InputEvent
Private
	Field keyCode:Int
	Field keyChar:Int

Public
	Method KeyCode:Int() Property
		Return keyCode
	End
	Method KeyChar:Int() Property
		Return keyChar
	End

	Method New(eventType:Int)
		Super.New(eventType)
	End
End

Private

Class InputEventEnumerator
Private
	Field ic:InputCache
	Field index:Int
	Field eventType:Int

Public
	Method New(ic:InputCache, eventType:Int)
		Self.ic = ic
		Self.eventType = eventType
	End

	Method Reset:Void()
		index = 0
	End
End

' enumerator class to handle KeyEvents
Class KeyEventEnumerator Extends InputEventEnumerator
Private
	Field event:KeyEvent

Public
	Method New(ic:InputCache, eventType:Int)
		Super.New(ic, eventType)
		Self.event = New KeyEvent
	End

	Method HasNext:Bool()
		If eventType = EVENT_KEY_DOWN Then Return index < ic.keyDownCount
		If eventType = EVENT_KEY_RELEASED Then Return index < ic.keyReleasedCount
		If eventType = EVENT_KEY_HIT Then Return index < ic.keyHitCount
		Return False
	End

	Method NextObject:KeyEvent()
		Assert(HasNext())
		Local idx:Int = 0
		If eventType = EVENT_KEY_DOWN Then
			idx = ic.currentKeysDown[index]
			event.eventTime = ic.keyDownTime[idx]
		ElseIf eventType = EVENT_KEY_RELEASED Then
			idx = ic.currentKeysReleased[index]
			event.eventTime = ic.keyReleasedTime[idx]
		ElseIf eventType = EVENT_KEY_HIT Then
			idx = ic.currentKeysHit[index]
			event.eventTime = ic.keyHitTime[idx]
		End
		index += 1
		event.shiftDown = ic.keyDown[KEY_SHIFT]<>0
		event.ctrlDown = ic.keyDown[KEY_CONTROL]<>0
		event.altDown = ic.keyDown[VK_ALT]<>0
		event.keyCode = idx
		event.keyChar = CharForCode(idx, event.shiftDown)
		Return event
	End
End

' i'd love to use <T Extends InputEventEnumerator> but monkey doesn't support it
Class EnumWrapper<T>
Private
	Field wrappedEnum:T

Public
	Method New(wrappedEnum:T)
		Self.wrappedEnum = wrappedEnum
	End

	Method ObjectEnumerator:T()
		If InputEventEnumerator(wrappedEnum) <> Null Then InputEventEnumerator(wrappedEnum).Reset()
		Return wrappedEnum
	End
End

Function CharForCode:Int(code:Int, shiftDown:Bool)
	BuildCharForCodeArray()
	If shiftDown Then
		Return charForShiftCodeArray[code]
	Else
		Return charForCodeArray[code]
	End
End

Private

Class TouchData
	Const FLING_SAMPLE_RATE:Int = 10

	Field firstTouchX:Int
	Field firstTouchY:Int
	Field firstTouchTime:Int
	Field lastTouchX:Int
	Field lastTouchY:Int
	Field flingSamplesX:Int[FLING_SAMPLE_RATE]
	Field flingSamplesY:Int[FLING_SAMPLE_RATE]
	Field flingSamplesTime:Int[FLING_SAMPLE_RATE]
	Field flingSampleCount:Int
	Field flingSampleNext:Int

	' calculated values
	Field movedTooFar:Bool
	Field testedLongPress:Bool
	Field firedLongPress:Bool
	Field distanceMovedX:Int
	Field distanceMovedY:Int
	Field touchVelocityX:Float
	Field touchVelocityY:Float
	Field touchVelocitySpeed:Float

	Method Reset:Void(x:Int, y:Int)
		firstTouchX = x
		firstTouchY = y
		lastTouchX = x
		lastTouchY = y
		firstTouchTime = Int(dt.currentticks)
		testedLongPress = False
		firedLongPress = False
		For Local i:Int = 0 Until FLING_SAMPLE_RATE
			flingSamplesX[i] = 0
			flingSamplesY[i] = 0
			flingSamplesTime[i] = 0
		Next
		flingSampleCount = 0
		flingSampleNext = 0
		movedTooFar = False
		touchVelocityX = 0
		touchVelocityY = 0
		touchVelocitySpeed = 0
		AddFlingSample(x, y)
	End

	Method AddFlingSample:Void(x:Int, y:Int)
		flingSamplesX[flingSampleNext] = x
		flingSamplesY[flingSampleNext] = y
		flingSamplesTime[flingSampleNext] = Int(dt.currentticks)
		If flingSampleCount < FLING_SAMPLE_RATE Then flingSampleCount += 1
		flingSampleNext += 1
		If flingSampleNext >= FLING_SAMPLE_RATE Then flingSampleNext = 0

		' find the first and last samples
		Local first:Int = flingSampleNext - flingSampleCount
		Local last:Int = flingSampleNext - 1
		While first < 0
			first += FLING_SAMPLE_RATE
		End
		While last < 0
			last += FLING_SAMPLE_RATE
		End

		' get the total delta
		If flingSampleCount > 0 Then
			' calculate the velocity in pixels per second
			Local secs:Float = Float(flingSamplesTime[last] - flingSamplesTime[first]) / 1000
			touchVelocityX = (flingSamplesX[last] - flingSamplesX[first]) / secs
			touchVelocityY = (flingSamplesY[last] - flingSamplesY[first]) / secs
			touchVelocitySpeed = Sqrt(touchVelocityX * touchVelocityX + touchVelocityY * touchVelocityY)
		End
	End

	Method Update:Void(x:Int, y:Int)
		' update distance
		distanceMovedX = x - lastTouchX
		distanceMovedY = y - lastTouchY
		lastTouchX = x
		lastTouchY = y
		' update the fling
		AddFlingSample(x, y)
		' check to see if we moved far enough away that we won't count this as a click
		If Not movedTooFar Then
			' get the first delta
			Local dx:Int = x - firstTouchX
			Local dy:Int = y - firstTouchY
			If dx * dx + dy * dy > InputCache.IGNORE_CLICK_DISTANCE * InputCache.IGNORE_CLICK_DISTANCE Then
				movedTooFar = True
			End
		End
	End
End

' Builds up arrays to convert key codes to characters.
' This is based off a US keyboard layout (sorry!)
' I may look at doing something native to correctly match keyboards to locale.
' warning: 107 seems to be the code for both the EQUALS in the number row and the PLUS on the number pad... wtf?
Function BuildCharForCodeArray:Void()
	If charForCodeArray.Length > 0 Then Return
	charForCodeArray = New Int[InputCache.INPUT_COUNT]
	charForShiftCodeArray = New Int[InputCache.INPUT_COUNT]

	' letters
	For Local i:Int = KEY_A To KEY_Z
		charForCodeArray[i] = i+32
		charForShiftCodeArray[i] = i
	Next

	' numbers
	For Local i:Int = KEY_0 To KEY_9
		charForCodeArray[i] = i
	Next

	' keypad numbers
	For Local i:Int = 96 To 105
		charForCodeArray[i] = KEY_0 + i - 96
	Next

	' shift-numbers
	charForShiftCodeArray[KEY_0] = ASC_CLOSE_PARENTHESIS
	charForShiftCodeArray[KEY_1] = ASC_EXCLAMATION
	charForShiftCodeArray[KEY_2] = ASC_AT
	charForShiftCodeArray[KEY_3] = ASC_HASH
	charForShiftCodeArray[KEY_4] = ASC_DOLLAR
	charForShiftCodeArray[KEY_5] = ASC_PERCENT
	charForShiftCodeArray[KEY_6] = ASC_CIRCUMFLEX
	charForShiftCodeArray[KEY_7] = ASC_AMPERSAND
	charForShiftCodeArray[KEY_8] = ASC_ASTERISK
	charForShiftCodeArray[KEY_9] = ASC_OPEN_PARENTHESIS

	charForCodeArray[VK_SEMICOLON] = ASC_SEMICOLON
	charForCodeArray[VK_EQUALS] = ASC_EQUALS
	charForCodeArray[KEY_COMMA] = ASC_COMMA
	charForCodeArray[VK_HYPHEN] = ASC_HYPHEN
	charForCodeArray[KEY_PERIOD] = ASC_PERIOD
	charForCodeArray[KEY_SLASH] = ASC_SLASH
	charForCodeArray[KEY_TILDE] = ASC_BACKTICK
	charForCodeArray[KEY_OPENBRACKET] = ASC_OPEN_BRACKET
	charForCodeArray[KEY_CLOSEBRACKET] = ASC_CLOSE_BRACKET
	charForCodeArray[KEY_QUOTES] = ASC_DOUBLE_QUOTE
	charForCodeArray[VK_BACKSLASH] = ASC_BACKSLASH
	charForCodeArray[KEY_SPACE] = ASC_SPACE

	charForShiftCodeArray[VK_SEMICOLON] = ASC_COLON
	charForShiftCodeArray[VK_EQUALS] = ASC_PLUS
	charForShiftCodeArray[KEY_COMMA] = ASC_LESS_THAN
	charForShiftCodeArray[VK_HYPHEN] = ASC_UNDERSCORE
	charForShiftCodeArray[KEY_PERIOD] = ASC_GREATER_THAN
	charForShiftCodeArray[KEY_SLASH] = ASC_QUESTION
	charForShiftCodeArray[KEY_TILDE] = ASC_TILDE
	charForShiftCodeArray[KEY_OPENBRACKET] = ASC_OPEN_BRACE
	charForShiftCodeArray[KEY_CLOSEBRACKET] = ASC_CLOSE_BRACE
	charForShiftCodeArray[KEY_QUOTES] = ASC_SINGLE_QUOTE
	charForShiftCodeArray[VK_BACKSLASH] = ASC_PIPE
	charForShiftCodeArray[KEY_SPACE] = ASC_SPACE
End


Fixes for correct FlxText overloading

Fixes for correct FlxText overloading

Strict

Import flxextern
Import flxsprite
Import flxcamera
Import flxtext.driver
Import flxtext.driver.native
Import flxg

Import system.flxcolor
Import system.flxassetsmanager

Class FlxText Extends FlxSprite

	Global _CLASS:FlxClass = new FlxTextClass()

	Const ALIGN_LEFT:Float = 0
	Const ALIGN_RIGHT:Float = 1
	Const ALIGN_CENTER:Float = .5

	Const DRIVER_NATIVE:Int = 0
	Const DRIVER_FONTMACHINE:Int = 1
	'ANGELFONT must be last
	Const DRIVER_ANGELFONT:Int = 2

	Const SYSTEM_FONT:String = "system"

Private
	Global _defaultDriver:FlxClass = NATIVE_TEXT_DRIVER
	Field _driver:FlxTextDriver
	Field _shadow:FlxColor

Public
	Method New(x:Float, y:Float, width:Int, text:FlxStringable, driver:FlxClass = Null)
		Super.New(x, y)
		_Construct(width, driver)
		Text = text
	End Method

	Method New(x:Float, y:Float, width:Int = 0, text:String = "", driver:FlxClass = Null)
		Super.New(x, y)
		_Construct(width, driver)
		Text = text
	End Method

	Method Update:Void()
		_driver.Update()
		Super.Update()
	End Method

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

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

	Method Text:String() Property
		Return _driver.Text
	End Method

	Method Text:Void(text:String) Property
		_driver.Text = text

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

	Method Text:Void(text:FlxStringable) Property
		_driver.Text = text

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

	Method Size:Void(size:Int) Property
		_driver.Size = FlxAssetsManager.GetValidFontSize(_driver.Font, size, _driver.ID)

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

	Method Size:Int() Property
		Return _driver.Size
	End Method

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

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

	Method Font:String() Property
		Return _driver.Font
	End Method

	Method Font:Void(font:String) Property
		_driver.Font = font
	End Method

	Method Alignment:Float() Property
		Return _driver.Alignment
	End Method

	Method Alignment:Void(alignment:Float) Property
		_driver.Alignment = alignment

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

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

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

	Method _DrawSurface:Void(x:Float, y:Float)
		If (_shadow.argb <> 0) Then
			Local oldColor:Int = FlxG._lastDrawingColor
			Local oldAlpha:Int = FlxG._lastDrawingAlpha
			Local camera:FlxCamera = FlxG.camera

			If (camera.Color <> FlxG.WHITE) Then
				_mixedColor.MixRGB(_shadow, camera._GetColorObject())

				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
			_driver.Draw(x+1, y+1)

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

		_driver.Draw(x, y)
	End Method

	Function SetDefaultDriver:Void(driver:FlxClass)
		_defaultDriver = driver
	End Function

	Method ToString:String()
		Return "FlxText"
	End Method

Private
	Method _Construct:Void(width:Int = 0, driver:FlxClass = Null)
		Pixels = Null

		_color = New FlxColor()
		_shadow = New FlxColor(0)

		If (driver = Null) driver =	_defaultDriver
		_driver = FlxTextDriver(driver.CreateInstance())

		Self.width = width
		frameWidth = Self.width

		_driver.Width = width
		SetFormat(SYSTEM_FONT)
	End Method

End Class

Private
Class FlxTextClass Implements FlxClass

	Method CreateInstance:Object()
		Return New FlxText()
	End Method

	Method InstanceOf:Bool(object:Object)
		Return (FlxText(object) <> Null)
	End Method

End Class

Strict

Import flxextern
Import flxsprite
Import flxcamera
Import flxtext.driver
Import flxtext.driver.native
Import flxg

Import system.flxcolor
Import system.flxassetsmanager

Class FlxText Extends FlxSprite

	Global _CLASS:FlxClass = new FlxTextClass()

	Const ALIGN_LEFT:Float = 0
	Const ALIGN_RIGHT:Float = 1
	Const ALIGN_CENTER:Float = .5

	Const DRIVER_NATIVE:Int = 0
	Const DRIVER_FONTMACHINE:Int = 1
	'ANGELFONT must be last
	Const DRIVER_ANGELFONT:Int = 2

	Const SYSTEM_FONT:String = "system"

Private
	Global _defaultDriver:FlxClass = NATIVE_TEXT_DRIVER
	Field _driver:FlxTextDriver
	Field _shadow:FlxColor

Public
	Method New(x:Float, y:Float, width:Int = 0, text:FlxStringable = Null, driver:FlxClass = Null)
		Super.New(x, y)
		_Construct(width, driver)
		Text = text
	End Method

	Method New(x:Float, y:Float, width:Int = 0, text:String = "", driver:FlxClass = Null)
		Super.New(x, y)
		_Construct(width, driver)
		Text = text
	End Method

	Method Update:Void()
		_driver.Update()
		Super.Update()
	End Method

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

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

	Method Text:String() Property
		Return _driver.Text
	End Method

	Method Text:Void(text:String) Property
		_driver.Text = text

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

	Method Text:Void(text:FlxStringable) Property
		_driver.Text = text

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

	Method Size:Void(size:Int) Property
		_driver.Size = FlxAssetsManager.GetValidFontSize(_driver.Font, size, _driver.ID)

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

	Method Size:Int() Property
		Return _driver.Size
	End Method

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

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

	Method Font:String() Property
		Return _driver.Font
	End Method

	Method Font:Void(font:String) Property
		_driver.Font = font
	End Method

	Method Alignment:Float() Property
		Return _driver.Alignment
	End Method

	Method Alignment:Void(alignment:Float) Property
		_driver.Alignment = alignment

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

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

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

	Method _DrawSurface:Void(x:Float, y:Float)
		If (_shadow.argb <> 0) Then
			Local oldColor:Int = FlxG._lastDrawingColor
			Local oldAlpha:Int = FlxG._lastDrawingAlpha
			Local camera:FlxCamera = FlxG.camera

			If (camera.Color <> FlxG.WHITE) Then
				_mixedColor.MixRGB(_shadow, camera._GetColorObject())

				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
			_driver.Draw(x+1, y+1)

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

		_driver.Draw(x, y)
	End Method

	Function SetDefaultDriver:Void(driver:FlxClass)
		_defaultDriver = driver
	End Function

	Method ToString:String()
		Return "FlxText"
	End Method

Private
	Method _Construct:Void(width:Int = 0, driver:FlxClass = Null)
		Pixels = Null

		_color = New FlxColor()
		_shadow = New FlxColor(0)

		If (driver = Null) driver =	_defaultDriver
		_driver = FlxTextDriver(driver.CreateInstance())

		Self.width = width
		frameWidth = Self.width

		_driver.Width = width
		SetFormat(SYSTEM_FONT)
	End Method

End Class

Private
Class FlxTextClass Implements FlxClass

	Method CreateInstance:Object()
		Return New FlxText()
	End Method

	Method InstanceOf:Bool(object:Object)
		Return (FlxText(object) <> Null)
	End Method

End Class


FlxText Size method fixed

FlxText Size method fixed

Strict

Import flxextern
Import flxsprite
Import flxcamera
Import flxtext.driver
Import flxtext.driver.native
Import flxg

Import system.flxcolor
Import system.flxassetsmanager

Class FlxText Extends FlxSprite

	Global ClassObject:FlxClass = new FlxTextClass()

	Const ALIGN_LEFT:Float = 0
	Const ALIGN_RIGHT:Float = 1
	Const ALIGN_CENTER:Float = .5

	Const DRIVER_NATIVE:Int = 0
	Const DRIVER_FONTMACHINE:Int = 1
	'ANGELFONT must be last
	Const DRIVER_ANGELFONT:Int = 2

	Const SYSTEM_FONT:String = "system"

Private
	Global _defaultDriver:FlxClass = NativeTextDriver
	Field _driver:FlxTextDriver
	Field _shadow:FlxColor

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

		Pixels = Null

		_color = New FlxColor()
		_shadow = New FlxColor(0)

		If (driver = Null) driver =	_defaultDriver
		_driver = FlxTextDriver(driver.CreateInstance())

		Self.width = width
		frameWidth = Self.width

		_driver.Width = width
		SetFormat(SYSTEM_FONT)
		Text = text
	End Method

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

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

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

		_driver.Width = width
		_ResetHelpers()
	End Method

	Method Text:String() Property
		Return _driver.Text
	End Method

	Method Text:Void(text:String) Property
		_driver.Text = text

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

	Method Size:Void(size:Int) Property
		_driver.Size = FlxAssetsManager.FlxAssetsManager.GetFont(_driver.Font, _driver.ID).GetValidSize(size)

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

	Method Size:Int() Property
		Return _driver.Size
	End Method

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

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

	Method Font:String() Property
		Return _driver.Font
	End Method

	Method Font:Void(font:String) Property
		_driver.Font = font
	End Method

	Method Alignment:Float() Property
		Return _driver.Alignment
	End Method

	Method Alignment:Void(alignment:Float) Property
		_driver.Alignment = alignment

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

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

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

	Method _DrawSurface:Void(x:Float, y:Float)
		If (_shadow.argb <> 0) Then
			Local oldColor:Int = FlxG._lastDrawingColor
			Local oldAlpha:Int = FlxG._lastDrawingAlpha
			Local camera:FlxCamera = FlxG.camera

			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
			_driver.Draw(x+1, y+1)

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

		_driver.Draw(x, y)
	End Method

	Function SetDefaultDriver:Void(driver:FlxClass)
		_defaultDriver = driver
	End Function

	Method ToString:String()
		Return "FlxText"
	End Method

End Class

Private
Class FlxTextClass Implements FlxClass

	Method CreateInstance:Object()
		Return New FlxText()
	End Method

	Method InstanceOf:Bool(object:Object)
		Return (FlxText(object) <> Null)
	End Method

End Class

Strict

Import flxextern
Import flxsprite
Import flxcamera
Import flxtext.driver
Import flxtext.driver.native
Import flxg

Import system.flxcolor
Import system.flxassetsmanager

Class FlxText Extends FlxSprite

	Global ClassObject:FlxClass = new FlxTextClass()

	Const ALIGN_LEFT:Float = 0
	Const ALIGN_RIGHT:Float = 1
	Const ALIGN_CENTER:Float = .5

	Const DRIVER_NATIVE:Int = 0
	Const DRIVER_FONTMACHINE:Int = 1
	'ANGELFONT must be last
	Const DRIVER_ANGELFONT:Int = 2

	Const SYSTEM_FONT:String = "system"

Private
	Global _defaultDriver:FlxClass = NativeTextDriver
	Field _driver:FlxTextDriver
	Field _shadow:FlxColor

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

		Pixels = Null

		_color = New FlxColor()
		_shadow = New FlxColor(0)

		If (driver = Null) driver =	_defaultDriver
		_driver = FlxTextDriver(driver.CreateInstance())

		Self.width = width
		frameWidth = Self.width

		_driver.Width = width
		SetFormat(SYSTEM_FONT)
		Text = text
	End Method

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

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

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

		_driver.Width = width
		_ResetHelpers()
	End Method

	Method Text:String() Property
		Return _driver.Text
	End Method

	Method Text:Void(text:String) Property
		_driver.Text = text

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

	Method Size:Void(size:Int) Property
		_driver.Size = FlxAssetsManager.GetValidFontSize(_driver.Font, size, _driver.ID)

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

	Method Size:Int() Property
		Return _driver.Size
	End Method

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

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

	Method Font:String() Property
		Return _driver.Font
	End Method

	Method Font:Void(font:String) Property
		_driver.Font = font
	End Method

	Method Alignment:Float() Property
		Return _driver.Alignment
	End Method

	Method Alignment:Void(alignment:Float) Property
		_driver.Alignment = alignment

		Self.height = _driver.GetTextHeight()
		frameHeight = Self.height
		_ResetHelpers()
	End Method

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

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

	Method _DrawSurface:Void(x:Float, y:Float)
		If (_shadow.argb <> 0) Then
			Local oldColor:Int = FlxG._lastDrawingColor
			Local oldAlpha:Int = FlxG._lastDrawingAlpha
			Local camera:FlxCamera = FlxG.camera

			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
			_driver.Draw(x+1, y+1)

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

		_driver.Draw(x, y)
	End Method

	Function SetDefaultDriver:Void(driver:FlxClass)
		_defaultDriver = driver
	End Function

	Method ToString:String()
		Return "FlxText"
	End Method

End Class

Private
Class FlxTextClass Implements FlxClass

	Method CreateInstance:Object()
		Return New FlxText()
	End Method

	Method InstanceOf:Bool(object:Object)
		Return (FlxText(object) <> Null)
	End Method

End Class