File parser for DirectGUI

This is a file parser for DirectGUI. It uses the ini file format, I’ve mainly wrote this for the community where I’m in, which kind of needs somehow a file standard for those DirectGUI stuff. I’ve did XML before, but that didn’t work out very well (thanks treeform). This parser is useful in some ways:

  • It isn’t that much slower as normal python code (currently 4 gui objects require 0.018 seconds for parsing/attaching them to a nodepath)
  • This is a slighty more clear then adding some attributes @ the constructor and then using Direct[’ '] or Direct.setPos().
  • It’s easy to share UI interfaces with this, when you only need to ship the file and just need 2 lines to implement it into your game.

The features currently are:

  • Parsing a file on a specified nodepath (you can parse the same file on more nodepaths, or other way around)
  • Reloading while runtime (pretty useful)
  • Everything is in the ini file format.

How to use:

  • Create a new INI file.
  • Create a section with the variable name which should hold the GUI object (you can retrieve this during runtime in the returned dict from GUIParser.parse() or GUIParser.variable)
  • Add the items you wish

If you have improvements, bug fixes, comments or anything else, feel free to post it. The parser still isn’t completed and far from optimized, it only allows a few NodePath options now, you should be able some yourself if you need them, or just request them.

Here’s an example showing how to use it.

Python code:

from pandac.PandaModules import loadPrcFileData, KeyboardButton, MouseButton, WindowProperties, TextNode
from direct.gui.DirectGui import *
import direct.directbase.DirectStart
import GUIParser

parser = GUIParser.GUIParser()
dict = parser.parse("test.ini", render2d)

def reload():
	parser.reload("test.ini", render2d)

base.accept('r', reload)
 
run()

GUI File (test.ini):

[button1]
type=DirectButton
pos=(1,0,0)
color=(255,0,0,0.4)

[frame1]
type=DirectEntry
pos=(0.2,0,0)
color=(255,0,0,0.4)
scale=0.2
framecolor=(255,0,0,0.4)
text=Hi

[frame2]
type=DirectEntry
pos=(-0.4,0,0)
text=Hi
text_bg=(255,0,0,0.4)
text_scale=0.5
scale=0.4

[button2]
type=DirectButton
pos=(-1,0,0)
text=Button

Source code:

from direct.gui.DirectGui import *
import ConfigParser
import time

classNames = {
	'DirectButton':DirectButton,
	'DirectCheckButton':DirectCheckButton,
	'DirectRadioButton':DirectRadioButton,
	'DirectDialog':DirectDialog,
	'DirectFrame':DirectFrame,
	'DirectLabel':DirectLabel,
	'DirectEntry':DirectEntry,
	'DirectOptionMenu':DirectOptionMenu,
	'DirectScrolledList':DirectScrolledList,
	'DirectWaitBar':DirectWaitBar,
	'DirectSlider':DirectSlider,
	'DirectScrollBar':DirectScrollBar,
	'DirectScrolledFrame':DirectScrolledFrame,
}

class GUIParser(object):
	def __init__(self, filename=None, nodepath=None):
		"""Constructor of the GUIParser class. It's possible to specify filename/nodepath at instancing"""
		self.filename = filename
		self.nodepath = nodepath
		self.dict = {}


	def parse(self, filename=None, nodepath=None):
		"""Parses a single file.
		If filename and nodepath are specified, they will get used.
		If not, we will use the values stored when we got instanced."""
		filename = filename or self.filename
		nodepath = nodepath or self.nodepath
		self.config = ConfigParser.ConfigParser()
		self.config.read(filename)
		start = time.clock()
		for section in self.config.sections():
			name = section
			type = self.config.get(name, "type")
			instance = classNames[type]()
			setattr(self, name, instance)
			self.dict[name] = instance
			for option in self.config.options(section):
				self.parseAttribute(name, option, instance)
		end = time.clock()
		print "Time elapsed = ", end - start, "seconds"
		return self.dict

	def parseAttribute(self, name, attribute, instance):
		"""Parses a single attribute"""
		if attribute == 'pos':
			instance.setPos(tuple(self.parseTuple(self.config.get(name, attribute))))
		if attribute == 'hpr':
			instance.setHpr(tuple(self.parseTuple(self.config.get(name, attribute))))
		if attribute == 'color':
			instance.setColor(tuple(self.parseTuple(self.config.get(name, attribute))))
		if attribute == 'scale':
			text = self.config.get(name, attribute)
			if text.startswith("(") and text.endswith(")"):
				instance.setScale(float(self.parseTuple(text)))
			else:
				instance.setScale(float(self.config.get(name, attribute)))
		if attribute == 'frameColor':
			instance['frameColor'] = tuple(self.parseTuple(self.config.get(name, attribute)))
		if attribute.startswith('text'):
			if attribute == 'text':
				instance['text'] = self.config.get(name, attribute)
				instance.setText()
			elif attribute == 'text_bg':
				instance['text_bg'] = tuple(self.parseTuple(self.config.get(name, attribute)))
			elif attribute == 'text_fg':
				instance['text_fg'] = tuple(self.parseTuple(self.config.get(name, attribute)))
			elif attribute == 'text_pos':
				instance['text_fg'] = tuple(self.parseTuple(self.config.get(name, attribute)))
			elif attribute == 'text_scale':
				text = self.config.get(name, attribute)
				if text.startswith("(") and text.endswith(")"):
					instance['text_scale'] = float(self.parseTuple(text))
				else:
					instance['text_scale'] = float(text)
		if attribute == 'dgg':
			text = self.config.get(name, attribute)
			if text == 'DGG.NORMAL':
				instance['state'] = DGG.NORMAL
			elif text == 'DGG.DISABLED':
				instance['state'] = DGG.DISABLED
		if attribute.startswith('image'):
			if attribute == 'image':
				instance['image'] = self.config.get(name, attribute)
			elif attribute == 'image_pos':
				instance['image_pos'] = tuple(self.parseTuple(self.config.get(name, attribute)))
			elif attribute == 'image_hpr':
				instance['image_hpr'] = tuple(self.parseTuple(self.config.get(name, attribute)))
			elif attribute == 'image_scale':
				text = self.config.get(name, attribute)
				if text.startswith("(") and text.endswith(")"):
					instance['image_scale'] = float(self.parseTuple(text))
				else:
					instance['image_scale'] = float(text)
				

	def parseTuple(self, tuple):
		"""Parses a tuple from a string, returning a list, where you can use tuple(list) for a tuple again"""
		split = tuple[1:-1].split(',')
		return [float(x) for x in split]

	def destroyObjects(self):
		"""Removes all GUI objects from the nodepath"""
		for key, value in self.dict.items():
			value.destroy()

	def reload(self, filename=None, nodepath=None):
		"""Reloads all the GUI objects"""
		self.destroyObjects()
		self.parse(filename, nodepath)

I hope you guys like it, thanks :slight_smile:

oh no XML, stabs yes out!

dirtsimple.org/2004/12/python-is-not-java.html

Aw, dont be so mean. XML is not all that bad, if it’s used in the right situations, even though it may be less efficient than similar markup languages.

Ah, you’re 100% right. In java I’ve used XML for a dozen things, and after asking around in some python-related channels I see how bad it is.

rdb, you can close this topic if you like, since most likely no-one will use it, and I won’t be upgrading it anymore.

edit: Don’t – since my community somehow needs to make it easier for the layout I found ConfigParser. And currently it takes 0.02s to parse, which is not that bad… Will update thread soon.

The way you use XML looks like much typing and actually no improvement over python code.

Another idea:
How about creating a HTML-like parser? Everybody can do at least basic html. Problem with this is that you’d need bounding boxes (or rectangles) while processing the file as in HTML most things are placed relative to others.

For those who know CSS:
display property:
‘inline’ objects are impossible, but you could treat everything as block or inline-block.

position property:
For positioning in HTML+CSS you have the options ‘absolute’, ‘fixed’, ‘relative’, ‘static’ and ‘inherit’. Relevant for simple GUI parsing are ‘absolute’, ‘relative’ and ‘fixed’

Example of HTML-ish DirectGUI code (like a mix of html and css):

<DGUI>
<frame border="1px" color="(1.0, 0.5, 0.2)">
    <text font="Arial" size="80%">hello all</text>
    <button img="**/some_button" command="someMethod()" height="50px" width="30px" position="relative" margin-top="10px" align="center" />
    <button img="**/some_button" command="someMethod()" height="50px" width="30px" position="relative" margin-top="10px" align="center" />
</frame>
</DGUI>

Ok, this idea is in fact more about some convenience functions -like relative positioning to parents or pixel-perfect positioning- combined with a non-coder language.

Do you think something like that would be useful?

how about using json, i’ve actually never used it, but maybe a alternative.
othervise just use a dict like:

buttons = { 
  'button1': {
    'type' : 'DirectButton',
    'pos'  : (0.6,1,0),
    'text' : 'hi',
    'scale': 0.2,
    }
  }

I’m using ini files now – since my community wants something using files, and a bit higher level I’m using ini files. Currently I’m also writing a GuiWriter which would parse all DirectGui items of a to an ini file.

You guys have some nice ideas, and that HTML might actually work, but I prefer ini’s for now.

I’ve updated it with the ini parser, which is much faster, more optimized and has more options. It parses pretty fast (0.005-0.01 max per gui object). If anyone has improvements or something else, please post it!

The big problem with INI is that you can’t nest stuff.

yeah Hypnos json sound cool - its also a sub set of python:

def createButtons():
    buttons = { 
      'button1': { 
        'type' : 'DirectButton', 
        'pos'  : (0.6,1,0), 
        'text' : 'hi', 
        'scale': 0.2, 
        } 
      }
    gui.create(buttons)

Why just not use python then?