Open Source Console w/ Variable Watching

BlitzMax Forums/BlitzMax Programming/Open Source Console w/ Variable Watching

Since no one seems to be reading the OpenConsole thread, I thought I might post my contribution here

Its a console with debug variable watching / changing.
(I'm sure there could be some improvement here)

It is very easy to add commands, and should be easy to extend/change for use in your application. If you make any improvements why not post them here?

Example program:
SuperStrict
Include "includes/console.bmx"
Include "includes/debug.bmx"

Graphics 640,480
SetBlend ALPHABLEND


'Some example console commands:
Local _HelpText:String = "Passing a command name will return|"+ ..
				         "help information on that command.|"+ ..
				         "Passing the 'all' parmeter instead|"+ ..
				         "will display a list of available commands."
Debug.Console.RegisterCommand("Help","S",cmd_Help,_HelpText)
Debug.Console.RegisterCommand("?","S",cmd_Help,_HelpText)
Function cmd_Help(_cmd:TConsoleCommand)
	Local cmd:TConsoleCommand = _cmd.Console.FindCommand(_cmd.Parameters[0])
	If cmd
		Local help:String = _cmd.Mask.ToLower()
		help = help.Replace("i","Int, ")
		help = help.Replace("f","Float, ")
		help = help.Replace("s","String, ")
		help = help[..help.Length-2]
		_cmd.Console.Output ""
		_cmd.Console.Output "USAGE:"
		_cmd.Console.Output "  /"+_cmd.Name+" "+help
		_cmd.Console.Output ""
		_cmd.Console.Output "DESCRIPTION:"
		Local helpt:String [] = StringToStringArray(_cmd.HelpText,"|")
		For help = EachIn helpt
			_cmd.Console.Output "  "+help
		Next
	ElseIf _cmd.Parameters[0].ToLower() = "all"
		_cmd.Console.Output "AVAILABLE COMMANDS:"
		For Local c:TConsoleCommand = EachIn _cmd.Console.CommandList
			Local help:String = c.Mask.ToLower()
			help = help.Replace("i","Int, ")
			help = help.Replace("f","Float, ")
			help = help.Replace("s","String, ")
			help = help[..help.Length-2]
			_cmd.Console.Output "/"+c.Name+" "+help
		Next
	Else
		_cmd.Console.Output "ERROR: Command '" + _cmd.Parameters[0] + "' does not exist!"
	End If
End Function

Debug.Console.RegisterCommand("End","",cmd_End,"Quits Program.")
Debug.Console.RegisterCommand("Quit","",cmd_End,"Quits Program.")
Function cmd_End(_cmd:TConsoleCommand)
	End
End Function

Debug.Console.RegisterCommand("Set","SS",cmd_Set,"Sets a debug variables value.|Example:|  /Set VariableA, 200")
Function cmd_Set(_cmd:TConsoleCommand)
	Local _Name:String = _cmd.Parameters[0]
	For Local _dv:TDebugVariable = EachIn Debug.Variables
		If _dv.Name.ToLower().Trim() = _Name.ToLower().Trim()
			_dv.SetValue(Double(_cmd.Parameters[1].Trim()))
			Exit
		End If
	Next
End Function

Debug.Console.RegisterCommand("Ignore","S",cmd_Ignore,"Ignores a debug variable.")
Function cmd_Ignore(_cmd:TConsoleCommand)
	Debug.IgnoreVariable(_cmd.Parameters[0])
End Function

'Enable Debug & Console
Debug.Enable

'Create some variables to watch
Local MX:Float, MY:Float, RAD:Float
'Tell Debug to watch them
Debug.WatchFloat("MX",MX)
Debug.WatchFloat("MY",MY)
Debug.WatchFloat("RAD",RAD)

While Not KeyHit(KEY_ESCAPE)
		'Update  variables w/ mouse position
		MX = MouseX()
		MY = MouseY()
		
		Debug.Update
	Cls
		
		DrawOval MX-RAD,MY-RAD,RAD*2,RAD*2
		
		'Render console and debug variables
		Debug.Render
	Flip
Wend


debug.bmx:
Manages the console and debug variables
Global Debug:TDebug = New TDebug

Type TDebug
	Field Variables:TList
	Field VarX:Float,VarY:Float
	Field Enabled:Byte
	Field Console:TConsole

	Method New()
		Variables:TList = CreateList()
		Console:TConsole = CreateConsole()
	End Method
	
	Method Enable()
		Enabled = True
		Console.Active = True
		Debug.Console.UpdatePosition
	End Method

	Method Disable()
		Enabled = False
		Console.Active = False
	End Method
	
	Method WatchInt (_Name:String, _Variable:Int Var)
		Local _dv:TDebugVariable = New TDebugVariable
		_dv.Kind = "%"
		_dv.Name = _Name
		_dv.ValueInt = Varptr _Variable
		Variables.AddLast(_dv)
	End Method

	Method WatchFloat (_Name:String, _Variable:Float Var)
		Local _dv:TDebugVariable = New TDebugVariable
		_dv.Kind = "#"
		_dv.Name = _Name
		_dv.ValueFloat = Varptr _Variable
		Variables.AddLast(_dv)
	End Method

	Method WatchDouble (_Name:String, _Variable:Double Var)
		Local _dv:TDebugVariable = New TDebugVariable
		_dv.Kind = "!"
		_dv.Name = _Name
		_dv.ValueDouble = Varptr _Variable
		Variables.AddLast(_dv)
	End Method
	
	Method IgnoreVariable (_Name:String)
		For Local _dv:TDebugVariable = EachIn Variables
			If _dv.Name.ToLower().Trim() = _Name.ToLower().Trim() Variables.Remove(_dv); Return
		Next
	End Method
	
	Method Render()
		If Not Enabled Return
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetScale 1,1
		SetRotation 0
		SetBlend ALPHABLEND
		SetAlpha 1
		SetColor 255,255,255
		Console.Render()
		Local i:Int = 0
		For Local _dv:TDebugVariable = EachIn Variables
			DrawText _dv.ToString(),VarX,VarY + (i*15)
			i :+ 1
		Next
	End Method

	Method Update()
		If Not Enabled Return
		Console.Update()
	End Method
End Type

Type TDebugVariable
	Field Name:String
	Field Kind:String
	Field ValueInt:Int Ptr
	Field ValueFloat:Float Ptr
	Field ValueDouble:Double Ptr
	Method ToString:String()
		Select Kind.ToLower().Trim()
			Case "%"
				Return Name + " = " + ValueInt[0]
			Case "#"
				Return Name + " = " + ValueFloat[0]
			Case "!"
				Return Name + " = " + ValueDouble[0]
		End Select
	End Method
	Method SetValue(_Value:Double)
		Select Kind.ToLower().Trim()
			Case "%"
				ValueInt[0] = Int(_Value)
			Case "#"
				ValueFloat[0] = Float(_Value)
			Case "!"
				ValueFloat[0] = Double(_Value)
		End Select
	End Method
End Type


console.bmx:
Const CONSOLE_TOPLEFT:Int = 0
Const CONSOLE_TOPCENTER:Int = 1
Const CONSOLE_TOPRIGHT:Int = 2
Function CreateConsole:TConsole (_Width:Float = 400, _Height:Float = 250, _Position:Int = 1)
	Local c:TConsole = New TConsole
	c.ToggleKey = KEY_TAB
	c.Width = _Width
	c.Height = _Height
	c.Position = _Position
	c.UpdatePosition
	c.Padding = 5
	c.Alpha = .5
	c.BackgroundColor = [128,128,128]
	c.TextColor = [255,255,255]
	c.CommandColor = [0,255,0]
	c.CursorTimer = MilliSecs()
	c.CursorInterval = 300
	c.CursorState = 1
	c.DisplayTextTimer = MilliSecs()
	c.DisplayTextInterval = 20000
	c.DisplayText = CreateList()
	c.History = CreateList() 
	c.CommandList = CreateList()
	Return c
End Function
Type TConsole
	Field Active:Int
	Field ToggleKey:Int
	Field X:Float, Y:Float, Width:Float, Height:Float, Padding:Int
	Field TargetY:Float
	Field Alpha:Float, BackgroundColor:Int[3], TextColor:Int[3], CommandColor:Int[3], Position:String
	Field CursorX:Int, CursorY:Int, CursorState:Int, CursorTimer:Long, CursorInterval:Int
	Field History:TList, HistoryPosition:Int
	Field DisplayText:TList, DisplayTextTimer:Long, DisplayTextInterval:Int
	Field InputBuffer:String
	Field CommandList:TList
	
	Method Update()
		If KeyHit(ToggleKey)
			If Active Then Active = False;TargetY=-Height Else Active = True;TargetY=0
			FlushKeys
		End If
		Y :+ (TargetY-Y)*.1
		If Not Active Return

		If MilliSecs() > CursorTimer + CursorInterval
			CursorTimer = MilliSecs()
			CursorState :* -1
		End If

		If MilliSecs() > DisplayTextTimer + DisplayTextInterval And DisplayText.Count()
			DisplayTextTimer = MilliSecs()
			DisplayText.Remove(DisplayText.Last())
		End If
		
		GetInput
	End Method
	
	Method GetInput()
		Local CharHeight:Int = TextHeight("E")
		Local CharWidth:Int = TextWidth("E") 
		Local CharPerLine:Int = (Width - Padding) / CharWidth
		
		Local Char:Int = GetChar()
		If (Char > 31) And (Char < 126)
			If (InputBuffer.Length < CharPerLine)
				InputBuffer = InputBuffer[..CursorX]+Chr(Char)+InputBuffer[CursorX..]
				CursorX :+ 1
			EndIf
		EndIf

		If Char = KEY_ENTER
			InputBuffer = InputBuffer.Trim()
			If InputBuffer.length > 0
				DisplayText.AddFirst(InputBuffer)
				History.AddFirst(InputBuffer)
				ProcessInput
				InputBuffer = ""
				CursorX = 0
				CursorY = 0
			End If
		EndIf
		
		If (DisplayText.Count() * CharHeight) > (height - (CharHeight + Padding))
			CursorY :+ (KeyDown(KEY_PAGEUP)-KeyDown(KEY_PAGEDOWN))*2
			If CursorY < 0 CursorY = 0
			If CursorY > (DisplayText.Count() * CharHeight)-(height - (CharHeight + Padding))
				CursorY = (DisplayText.Count() * CharHeight)-(height - (CharHeight + Padding))
			End If
		End If
		
		Local Key:Int = GetKey()
		Select Key
			Case KEY_UP
				If History.Count() > 0 Then
					If HistoryPosition < History.Count()-1 Then HistoryPosition :+ 1
					InputBuffer = String(History.ValueAtIndex(HistoryPosition))
					CursorX = InputBuffer.Length
				EndIf
			Case KEY_DOWN
				If History.Count() > 0 Then
					If HistoryPosition > -1 Then HistoryPosition :- 1
					If HistoryPosition < 0 Then 
						InputBuffer = ""
					Else
						InputBuffer = String(History.ValueAtIndex(HistoryPosition))
					EndIf
					CursorX = InputBuffer.Length
				EndIf
			Case KEY_LEFT
				If cursorX > 0 Then cursorX :- 1		
			Case KEY_RIGHT
				If cursorX < InputBuffer.Length Then cursorX :+ 1
			Case KEY_HOME
				CursorX = 0
			Case KEY_END
				CursorX = InputBuffer.Length
			Case KEY_BACKSPACE
				If CursorX > 0
					InputBuffer = InputBuffer[..(CursorX - 1)] + InputBuffer[CursorX..]
					CursorX :- 1
				EndIf  
			Case KEY_DELETE
				If CursorX < InputBuffer.Length
					InputBuffer = InputBuffer[..CursorX] + InputBuffer[(CursorX+1)..]
				EndIf  
		End Select
	End Method
	
	Method ProcessInput()
		If InputBuffer[..1] = "/"
			Local cmdN:String = InputBuffer[1..InputBuffer.Find(" ")]
			If InputBuffer.Find(" ") < 0 cmdN = InputBuffer[1..]
			Local cmd:TConsoleCommand = FindCommand(cmdN)
			If cmd
				Local _par:String[] = StringToStringArray(InputBuffer[InputBuffer.Find(" ")..],",")
				If InputBuffer.Find(" ") < 0 _par = Null
				cmd.Execute(_par)
			Else
				Output "ERROR: Command '" + cmdN + "' does not exist!"
			End If
		End If
	End Method
		
	Method Render()
		If Not Active And Y <= -Height  Return

		Local _Alpha:Float, _Color:Int[3], _Mode:Int
		_Alpha = GetAlpha()
		_Mode = GetBlend()
		GetColor _color[0],_color[1],_color[2]

		SetBlend ALPHABLEND
		SetAlpha Alpha
		SetColor BackgroundColor[0], BackgroundColor[1], BackgroundColor[2]
		
		Local CharHeight:Int = TextHeight("E") 
		Local CharWidth:Int = TextWidth("E") 
		
		DrawRect X,Y,width,height - (CharHeight + Padding + 1)
		DrawRect X,Y + height - (CharHeight + Padding), width, CharHeight + Padding
		
		SetAlpha Alpha * 2
		SetColor TextColor[0],TextColor[1],TextColor[2]
				
		If CursorState = 1 Then DrawRect X + Padding + (CursorX * CharWidth), Y + (height - Padding/2 - 2),CharWidth,2
		If InputBuffer[..1] = "/" And InputBuffer.Find(" ") > 0 
			SetColor CommandColor[0],CommandColor[1],CommandColor[2]
			DrawText InputBuffer[..InputBuffer.Find(" ")],X + Padding, Y + (height - (CharHeight + Padding/2))
			SetColor TextColor[0],TextColor[1],TextColor[2]
			DrawText InputBuffer[InputBuffer.Find(" ")..],X + Padding + InputBuffer[..InputBuffer.Find(" ")].Length * CharWidth, Y + (height - (CharHeight + Padding/2))
		ElseIf InputBuffer[..1] = "/"
			SetColor CommandColor[0],CommandColor[1],CommandColor[2]
			DrawText InputBuffer, X + Padding, Y + (height - (CharHeight + Padding/2))
			SetColor TextColor[0],TextColor[1],TextColor[2]
		Else
			DrawText InputBuffer, X + Padding, Y + (height - (CharHeight + Padding/2))
		End If
		SetViewport X,Y,width,height - (CharHeight + Padding + 1)
		Local MaxLines:Int = (height - (CharHeight + Padding)) / CharHeight 
		Local TextCount:Int
		For Local t:String = EachIn DisplayText
			If (TextCount * CharHeight) - CursorY < height - (CharHeight + Padding + 1)
				If t[..1] = "/" And t.Find(" ") > 0
					SetColor CommandColor[0],CommandColor[1],CommandColor[2]
					DrawText t[..t.Find(" ")] , X + Padding , Y + ( (height - (Padding * 4 + 1) - CharHeight) - (TextCount * CharHeight) ) + CursorY
					SetColor TextColor[0],TextColor[1],TextColor[2]
					DrawText t[t.Find(" ")..] , X + Padding + t[..t.Find(" ")].Length*CharWidth, Y + ( (height - (Padding * 4 + 1) - CharHeight) - (TextCount * CharHeight) ) + CursorY
				ElseIf t[..1] = "/"
					SetColor CommandColor[0],CommandColor[1],CommandColor[2]
					DrawText t , X + Padding , Y + ( (height - (Padding * 4 + 1) - CharHeight) - (TextCount * CharHeight) ) + CursorY
				SetColor TextColor[0],TextColor[1],TextColor[2]
				Else
					DrawText t , X + Padding , Y + ( (height - (Padding * 4 + 1) - CharHeight) - (TextCount * CharHeight) ) + CursorY
				End If
			End If
			TextCount :+ 1
		Next
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetBlend _Mode
		SetAlpha _Alpha
		SetColor _color[0], _color[1], _color[2]
	End Method
	
	Method UpdatePosition ()
		Select Position
			Case CONSOLE_TOPLEFT
				X = 0
				Y = 0
			Case CONSOLE_TOPCENTER
				X = GraphicsWidth()/2-Width/2			
				Y = 0
			Case CONSOLE_TOPRIGHT
				X = GraphicsWidth()-Width
				Y = 0
		End Select
	End Method
	
	Method FindCommand:TConsoleCommand (_Name:String)
		For Local cmd:TConsoleCommand = EachIn CommandList
			If cmd.Name.ToLower() = _Name.ToLower() Return cmd
		Next
	End Method
	
	Method RegisterCommand (_Name:String , _Mask:String , _Action(_cmd:TConsoleCommand) , _HelpText:String)
		Local cmd:TConsoleCommand = New TConsoleCommand
		cmd.Console = Self
		cmd.Name = _Name.Trim()
		cmd.Mask = _Mask.Trim()
		cmd.Action = _Action
		cmd.HelpText = _HelpText
		CommandList.AddLast(cmd)
	End Method
	
	Method Output(_str:String)
		DisplayText.AddFirst(_str)
	End Method
End Type

Type TConsoleCommand
	Field Console:TConsole
	Field Name:String
	Field Mask:String
	Field Parameters:String []
	Field Action(_cmd:TConsoleCommand)
	Field HelpText:String
	
	Method VerifyParams:Int(_Params:String[])
		If _Params.Length <> Mask.Length And Mask.Length > 0
			Console.Output "ERROR: Incorrect number of parameters"
			Return False
		End If
		Return True
	End Method
	
	Method Execute(_Params:String[])
		If Not VerifyParams(_Params) Return
		Parameters = _Params
		If Action <> Null Action(Self)
		Return
	End Method
End Type
	
Function StringToStringArray:String [] (_String:String, _Delimiter:String)
	Local TempArray:String [1]
	Local TempString:String
	While _String.Find(_Delimiter) <> -1
		TempString = _String[.._String.Find(_Delimiter)]
		_String = _String[TempString.Length + 1..]
		TempArray[TempArray.Length - 1] = TempString.Trim()
		TempArray = TempArray[..TempArray.Length + 1]
	Wend
	TempString = _String
	TempArray[TempArray.Length - 1] = TempString.Trim()
	Return TempArray
End Function

Function GetKey:Int()
	Const RepeatRate : Int = 100 'ms
	Global LastTime:Long = MilliSecs()
	Global LastKey:Int = 0
	Local key:Int = 0
		
	While  Key <= 226
		If KeyDown(key) Then Exit
		Key:+ 1
	Wend
	
	GCCollect()
	
	If Key = 227 Then Return 0
	
	If Key = LastKey Then
		If MilliSecs()-LastTime < RepeatRate Then Return 0
	EndIf
	
	LastTime = MilliSecs()
	LastKey = Key
	If Key < 227 Then Return Key Else Return 0
End Function


Hope this is useful to someone!

This might be intresting :)
I remeber that back on Borland c++ 3.1 there was a watch sistem that I really used

I should point out that you should be able to select a variable to watch from almost any scope. So you could even monitor a Local variable of your custom Types Method.

I can't remember how many times I needed to know the value of a variable at runtime and created a bunch of temporary code just for that. Having to remove it later. This has become invaluable to me, and squashing bugs or tweaking settings has become much quicker now. I only wish there were a way to watch strings in the same way.

This is great! Thanks a lot, I was thinking of writing something similar myself and you saved me a lot of work!