Making the user interface data-driven using XML

A common practice in user interface programming is the division of design and program logic. In this recipe you will learn how to use one of the XML processing API that comes as a part of the standard libraries of the Python runtime used by Panda3D.

Note

Different versions of Panda3D might use different versions of Python. Don't assume these versions to be compatible as they might introduce changes to the standard library. You can check the version of the included Python runtime by issuing the command ppython --version on the console prompt.

Getting ready

Before proceeding, you will need to follow the recipe Setting up the game structure found in Chapter 1 to have the proper project structure set and ready.

How to do it...

Complete the following tasks to create a data-driven user interface:

  1. Add a new source file to the project and name it GuiBuilder.py.
  2. Add the following code to GuiBuilder.py. GuiHandler is only a class stub. The following functions are not members of the GuiHandler class!
    from xml.etree.ElementTree import *
    from direct.gui.DirectGui import *
    from panda3d.core import *
    class GuiHandler:
    def __init__(self):
    self.controls = {}
    def GuiFromXml(fname, handler):
    elements = ElementTree()
    elements.parse(fname)
    handleButtons(elements, handler)
    handleLabels(elements, handler)
    handleEntries(elements, handler)
    handleRadioGroups(elements, handler)
    
  3. Next, add the functions for finding all button, label, entry box, radio group, and radio button control descriptions contained in the XML structure to GuiBuilder.py:
    def handleButtons(elements, handler):
    buttons = elements.findall("button")
    for button in buttons:
    createButton(button, handler)
    def handleLabels(elements, handler):
    labels = elements.findall("label")
    for label in labels:
    createLabel(label, handler)
    def handleEntries(elements, handler):
    entries = elements.findall("entry")
    for entry in entries:
    createEntry(entry, handler)
    def handleRadioGroups(elements, handler):
    rdoGroups = elements.findall("radiogroup")
    for group in rdoGroups:
    handleRadios(group, handler)
    def handleRadios(elements, handler):
    radios = elements.findall("radio")
    created = []
    for radio in radios:
    created.append(createRadio(radio, handler))
    for btn in created:
    btn.setOthers(created)
    
  4. Below the code you just added, paste the getParams() helper function. This function parses the parameters of an XML element and returns them in a dictionary:
    def getParams(element):
    params = {}
    params["scale"] = float(element.findtext("scale", 1))
    params["text"] = element.findtext("text", "")
    params["mayChange"] = int(element.findtext("mayChange", 0))
    params["width"] = float(element.findtext("width", 1))
    params["value"] = [int(element.findtext("value", 0))]
    params["variable"] = element.findtext("variable", "")
    params["name"] = element.findtext("name", "")
    params["command"] = element.findtext("command", "")
    fcolorElem = element.find("frameColor")
    if fcolorElem != None:
    r = fcolorElem.get("r", 0)
    g = fcolorElem.get("g", 0)
    b = fcolorElem.get("b", 0)
    a = fcolorElem.get("a", 0)
    color = Vec4(float(r), float(g), float(b), float(a))
    params["frameColor"] = color
    else:
    color = Vec4(0, 0, 0, 0)
    params["frameColor"] = color
    posElem = element.find("pos")
    if posElem != None:
    x = posElem.get("x", 0)
    y = posElem.get("y", 0)
    z = posElem.get("z", 0)
    pos = Vec3(float(x), float(y), float(z))
    params["pos"] = pos
    else:
    pos = Vec3(0, 0, 0)
    params["pos"] = pos
    return params
    
  5. The following code is the last you need to add to GuiBuilder.py. These functions create the actual user interface controls:
    def createButton(element, handler):
    params = getParams(element)
    assert params["command"] != ""
    assert params["name"] != ""
    button = DirectButton(text = params["text"],
    scale = params["scale"],
    command = getattr(handler, params["command"]),
    pos = params["pos"])
    handler.controls[params["name"]] = button
    def createLabel(element, handler):
    params = getParams(element)
    assert params["name"] != ""
    label = DirectLabel(text = params["text"],
    pos = params["pos"],
    scale = params["scale"],
    textMayChange = params["mayChange"],
    frameColor = params["frameColor"])
    handler.controls[params["name"]] = label
    def createEntry(element, handler):
    params = getParams(element)
    assert params["name"] != ""
    entry = DirectEntry(scale = params["scale"],
    pos = params["pos"],
    width = params["width"])
    handler.controls[params["name"]] = entry
    def createRadio(element, handler):
    params = getParams(element)
    assert params["variable"] != ""
    assert params["name"] != ""
    radio = DirectRadioButton(text = params["text"],
    variable = getattr(handler, params["variable"]),
    value = params["value"],
    scale = params["scale"],
    pos = params["pos"])
    handler.controls[params["name"]] = radio
    return radio
    
  6. Now open the Application.py file and implement the Application class like this:
    from direct.showbase.ShowBase import ShowBase
    from panda3d.core import *
    from GuiBuilder import GuiHandler, GuiFromXml
    class Application(ShowBase):
    def __init__(self):
    ShowBase.__init__(self)
    handler = MyHandler()
    GuiFromXml("gui.xml", handler)
    class MyHandler(GuiHandler):
    def __init__(self):
    GuiHandler.__init__(self)
    self.gender = [0]
    def setName(self):
    title = ""
    if self.gender[0]:
    title = "Mister"
    else:
    title = "Miss"
    self.controls["nameLbl"]["text"] = "Hello " + title + " " + self.controls["nameEnt"].get() + "!"
    
  7. Add a new file called gui.xml to the src directory.
  8. Add this code to the gui.xml file:
    <gui>
    <button>
    <name>helloBtn</name>
    <command>setName</command>
    <scale>0.1</scale>
    <text>Say Hello!</text>
    <pos x="0" y="0" z="-0.1"/>
    </button>
    <label>
    <name>nameLbl</name>
    <text>Hi, what's your name?</text>
    <pos x="0" y="0" z="0.4"/>
    <scale>0.1</scale>
    <mayChange>1</mayChange>
    <frameColor r="0" g="0" b="0" a="0"/>
    </label>
    <entry>
    <name>nameEnt</name>
    <scale>0.08</scale>
    <pos x="-0.4" y="0" z="0.15"/>
    <width>10</width>
    </entry>
    <radiogroup>
    <radio>
    <name>femaleRdo</name>
    <text>Female</text>
    <variable>gender</variable>
    <value>0</value>
    <scale>0.05</scale>
    <pos x="-0.08" y="0" z="0.05"/>
    </radio>
    <radio>
    <name>maleRdo</name>
    <text>Male</text>
    <variable>gender</variable>
    <value>1</value>
    <scale>0.05</scale>
    <pos x="0.16" y="0" z="0.05"/>
    </radio>
    </radiogroup>
    </gui>
    
  9. Press F6 to start the application:
How to do it...

How it works...

In our newly added GuiBuilder library, we first add the GuiHandler class, which will be used as a base class for even handling. The central piece of this library is the GuiFromXml() function, that loads and parses the given XML file and calls the functions we added in step 3. Within these functions, we instruct the ElementTree object to look for the tags that represent user interface controls. If the appropriate tag is found, the according creation function is called, which first gets the parameters for the new control and then calls into Panda3D's DirectGui library to create the requested user interface element. The GUI controls are also added to the controls dictionary, so event-handling code is able to reference other user interface controls.

In Application.py, we then just add a new class that is derived from GuiHandler, containing a method that will set the label to the name given by the user. All the constructor of our Application class does then is create a new instance of MyHandler and call the GuiFromXml() function we created before to load the user interface data.

Finally, we add the XML data that defines our user interface. The tag names reflect the names of the controls and their properties. What's added now is the<name> tag that allows us to reference the control by name in the controls dictionary of the handler class.

There's more...

The code shown in this recipe is only a starting point for a data-driven user interface implementation and far from complete. For one, the getParams() function does not support all parameters yet. Also, besides not supporting all user interface controls found in the DirectGui library, the implemented elements do not even use all the parameters they could.

What's not complete is meant to be finished—feel free to complete and modify the GuiBuilder library to be able to elegantly divide the code and structure of your user interfaces!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset