Graphical Console

cradle

06-04-2008 18:16:53

I wanted my game to have an in game console. So, inspired by the efforts of PixL, I present my PythonOgre InGame Console.



Is a nearly fully functional Python interpreter. So you have access to everything! Still needs a few sweeteners, like key-repeating, but it is functional so I thought I'd release it into the wild.


import ogre.renderer.OGRE as ogre
import ogre.io.OIS as OIS
import array
import code
import sys

## PythonOgre Console
# Usage:
# console = console.Console(self.root)
# console.addLocals({'root':self.root})
# console.show()
# # Then inject the keys pressed in your keyboard handler
# console.keyPressed(evt)
#
# PythonOgre Console is sort of singleton. All instances of Console objects
# share the same state. So simply create a console object anywhere you wish
# to use it and they will all point to whichever one was created first.
#
## TODO
# - Key repeating
# - PGUP PGDOWN history viewing
# - Linewrap if prompt too long
# - Write history to file
# - Read history from file
# - Make font look better

class Console(ogre.FrameListener):
CONSOLE_LINE_LENGTH = 85
CONSOLE_LINE_COUNT = 15
__shared_state = {}

def __init__(self, root = None):
self.__dict__ = self.__shared_state

ogre.FrameListener.__init__(self)

if root:
root.addFrameListener(self)

if 'isSetup' not in self.__dict__:
self.isSetup = True
else:
return

self.currentInputHistory = 0
self.inputHistory = []
self.outputHistory = []
self.prompt = array.array('b')
self.updateOverlay = True
self.multiLineInput = False

self.interpreter = code.InteractiveConsole({'console':self})

self.keyBinds = {
OIS.KC_RETURN: self._runPromptText,
OIS.KC_BACK: self._backspaceCharPrompt,
OIS.KC_DELETE: self._deleteCharPrompt,
OIS.KC_UP: self._prevPromptText,
OIS.KC_DOWN: self._nextPromptText,
OIS.KC_ESCAPE: self.hide,
OIS.KC_LEFT: self._moveCursorLeft,
OIS.KC_RIGHT: self._moveCursorRight,
OIS.KC_HOME: self._moveCursorHome,
OIS.KC_END: self._moveCursorEnd,
OIS.KC_TAB: self._tab,
}

self.height = 0.0
self.hide()

self.overlay = ogre.OverlayManager.getSingleton().getByName("Application/ConsoleOverlay")
self.overlay.show()
self.textbox = ogre.OverlayManager.getSingleton().getOverlayElement("Application/ConsoleText")
self.textoverlay = ogre.OverlayManager.getSingleton().getOverlayElement("Application/ConsoleTextOverlay")
self.textpanel = ogre.OverlayManager.getSingleton().getOverlayElement("Application/ConsolePanel")
self.textpanel.getMaterial().setLightingEnabled(True)
self.textpanel.getMaterial().setSceneBlending(ogre.SBT_TRANSPARENT_ALPHA)

def addLocals(self, localsDict):
for var in localsDict:
self.interpreter.locals[var] = localsDict[var]

def _tab(self):
self.prompt.insert(self.cursorPosition, 32) # WhiteSpace
self.cursorPosition += 1

def _moveCursorEnd(self):
self.cursorPosition = len(self.prompt)

def _moveCursorHome(self):
self.cursorPosition = 0

def _moveCursorLeft(self):
if self.cursorPosition > 0:
self.cursorPosition -= 1

def _moveCursorRight(self):
if self.cursorPosition < len(self.prompt):
self.cursorPosition += 1

def _deleteCharPrompt(self):
if len(self.prompt) > 0 and self.cursorPosition < len(self.prompt):
self.prompt.pop(self.cursorPosition)

def _backspaceCharPrompt(self):
if len(self.prompt) > 0 and self.cursorPosition > 0:
self.prompt.pop(self.cursorPosition-1)
self.cursorPosition -= 1

def setPrompt(self, byteArray):
self._prompt = byteArray
self.cursorPosition = len(self._prompt)

def getPrompt(self):
return self._prompt

prompt = property(getPrompt, setPrompt)

def _enableSysRedirect(self):
self._stderr, self._stdout = sys.stderr, sys.stdout
sys.stderr, sys.stdout = self, self

def _disableSysRedirect(self):
sys.stderr, sys.stdout = self._stderr, self._stdout

def _runPromptText(self):
if len(self.prompt) > 0 or self.multiLineInput:
self.outputHistory += [self.promptCursor + self.prompt.tostring()]
self._enableSysRedirect()
self.multiLineInput = self.interpreter.push(self.prompt.tostring())
self._disableSysRedirect()
self.inputHistory += [self.prompt]
self.prompt = self.prompt[0:0]
self.currentInputHistory = len(self.inputHistory)

def _prevPromptText(self):
if self.currentInputHistory > 0:
self.currentInputHistory -= 1
self.prompt = self.inputHistory[self.currentInputHistory][:]

def _nextPromptText(self):
if self.currentInputHistory+1 < len(self.inputHistory):
self.currentInputHistory += 1
self.prompt = self.inputHistory[self.currentInputHistory][:]

def _getVisible(self):
return self._visible

visible = property(_getVisible)

def hide(self):
self._visible = False

def show(self):
self._visible = True

def keyPressed(self, evt):
if not self.visible:
if evt.key == OIS.KC_GRAVE:
self.show()
return

try:
self.keyBinds[evt.key]()
except KeyError:
if evt.text != 0:
self.prompt.insert(self.cursorPosition, evt.text)
self.cursorPosition += 1
self.currentInputHistory = len(self.inputHistory)

self.updateOverlay = True

def _updateConsoleText(self):
lines = [""] * (Console.CONSOLE_LINE_COUNT - len(self.outputHistory))
lines += self.outputHistory[-1*Console.CONSOLE_LINE_COUNT:]
text = "\n".join(lines)
text += u"\n%s%s" % (self.promptCursor,
self.prompt.tostring())
self.textbox.setCaption(text)

text = "\n".join([""] * (Console.CONSOLE_LINE_COUNT))
text += u"\n%s%s_" % (self.promptCursor,
" " * self.cursorPosition)

self.textoverlay.setCaption(text)

def _appear(self, amt):
self.height += amt
self.overlay.show()
if self.height >= 1.0:
self.height = 1.0

def _dissapear(self, amt):
self.height -= amt
if self.height < 0.0:
self.height = 0.0
self.overlay.hide()

def _updateConsolePosition(self, time):
if self.visible and self.height < 1.0:
self._appear(3.5*time)
elif not self.visible and self.height > 0.0:
self._dissapear(3.5*time)

self.textpanel.setPosition(0,(self.height-1.0)*0.5)

def frameStarted(self, evt):
self._updateConsolePosition(evt.timeSinceLastFrame)

if self.updateOverlay:
self._updateConsoleText()
self.updateOverlay = False

return True

def _getPromptCursor(self):
return '... ' if self.multiLineInput else '>>> '

promptCursor = property(_getPromptCursor)

def write(self, text):
for line in text.split("\n"):
while len(line) > 0:
self.outputHistory += [line[:Console.CONSOLE_LINE_LENGTH]]
line = line[Console.CONSOLE_LINE_LENGTH:]

self.updateOverlay = True



And the materials

console.material

material console/background
{
technique
{
pass
{
diffuse 1 0 1 0.5
ambient 0.4 0.4 0.4
}

}

}


console.fontdef

Console
{
type truetype
source console.ttf
size 32
resolution 55
}


console.overlay

Application/ConsoleOverlay
{
zorder 500

container BorderPanel(Application/ConsolePanel)
{
material console/background
metrics_mode relative
left 0
top 0
width 1
height 0.5

element TextArea(Application/ConsoleText)
{
metrics_mode relative
left 0
top 0
width 1
height 1
font_name Console
char_height 0.031
}

element TextArea(Application/ConsoleTextOverlay)
{
metrics_mode relative
left 0
top 0
width 1
height 1
font_name Console
char_height 0.031
}
}
}


Download the fixedsys font

bharling

06-04-2008 22:13:03

Hey great!

Fancy putting this on the wiki?

cradle

07-04-2008 06:36:25

Fancy putting this on the wiki?
Done!

SiWi

07-04-2008 16:39:24

Great work. :D