PyCEGUI

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Revision as of 13:33, 13 March 2011 by Capek (Talk | contribs) (Robot: Cosmetic changes)

Jump to: navigation, search

Written for CEGUI 0.7


Works with versions 0.7.x (obsolete)

Requires at least version
0.7.5

Since CEGUI 0.7.5 official python bindings are provided. The main reason for that is that the new CEGUI tools are written completely in python. As a nice side effect, everybody can use the bindings standalone in their python only apps as well as embedded python apps for scripting and what not.

Where to get the bindings?

We provide ready to go packages for win32 platform since it's fairly hard to get things going there. You can get them at the pypi repository. For other platforms, please just download the SDK and use bindings you find there. You can also use Win32 bindings from the SDK if you use embedded python, it can be easier than installing the aforementioned package.

Base app

I will provide a little base application below so you know the basics of how to use CEGUI in python environment. OpenGL is used in the base app.

import os, sys
 
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
 
# you must have PyCEGUI python package installed (see pypi repository)
# or you must make it work yourself using binary bindings from SDK
import PyCEGUI
import PyCEGUIOpenGLRenderer
 
CEGUI_PATH = ""
 
class BaseApp(object):
    def __init__(self):
        glutInit(sys.argv)
        glutInitDisplayMode(GLUT_DEPTH|GLUT_DOUBLE|GLUT_RGBA)
        glutInitWindowSize(1280, 1024)
        glutInitWindowPosition(100, 100)
        glutCreateWindow("Crazy Eddie's GUI Mk-2 - glut Base Application")
        glutSetCursor(GLUT_CURSOR_NONE)
 
        glutDisplayFunc(self.displayFunc)
        glutReshapeFunc(self.reshapeFunc)
        glutMouseFunc(self.mouseFunc)
        glutMotionFunc(self.mouseMotionFunc)
        glutPassiveMotionFunc(self.mouseMotionFunc)
 
        PyCEGUIOpenGLRenderer.OpenGLRenderer.bootstrapSystem()
 
    def __del__(self):
        PyCEGUIOpenGLRenderer.OpenGLRenderer.destroySystem()
 
    def initialiseResources(self):
        rp = PyCEGUI.System.getSingleton().getResourceProvider()
 
        rp.setResourceGroupDirectory("schemes", CEGUI_PATH + "/datafiles/schemes")
        rp.setResourceGroupDirectory("imagesets", CEGUI_PATH + "/datafiles/imagesets")
        rp.setResourceGroupDirectory("fonts", CEGUI_PATH + "/datafiles/fonts")
        rp.setResourceGroupDirectory("layouts", CEGUI_PATH + "/datafiles/layouts")
        rp.setResourceGroupDirectory("looknfeels", CEGUI_PATH + "/datafiles/looknfeel")
        rp.setResourceGroupDirectory("schemas", CEGUI_PATH + "/datafiles/xml_schemas")
 
        PyCEGUI.Imageset.setDefaultResourceGroup("imagesets")
        PyCEGUI.Font.setDefaultResourceGroup("fonts")
        PyCEGUI.Scheme.setDefaultResourceGroup("schemes")
        PyCEGUI.WidgetLookManager.setDefaultResourceGroup("looknfeels")
        PyCEGUI.WindowManager.setDefaultResourceGroup("layouts")
 
        parser = PyCEGUI.System.getSingleton().getXMLParser()
        if parser.isPropertyPresent("SchemaDefaultResourceGroup"):
            parser.setProperty("SchemaDefaultResourceGroup", "schemas")     
 
    def setupUI(self):
        PyCEGUI.SchemeManager.getSingleton().create("VanillaSkin.scheme")
        PyCEGUI.SchemeManager.getSingleton().create("TaharezLook.scheme")
        PyCEGUI.System.getSingleton().setDefaultMouseCursor("Vanilla-Images", "MouseArrow")
 
        root = PyCEGUI.WindowManager.getSingleton().loadWindowLayout("VanillaWindows.layout")
        PyCEGUI.System.getSingleton().setGUISheet(root)
 
        self.wnd = PyCEGUI.WindowManager.getSingleton().createWindow("TaharezLook/FrameWindow", "Demo Window")
        root.addChildWindow(self.wnd)
 
    def run(self):
        self.initialiseResources()
        self.setupUI()
 
        self.lastFrameTime = glutGet(GLUT_ELAPSED_TIME)
        self.updateFPS = 0
        glutMainLoop()
 
    def displayFunc(self):
        thisTime = glutGet(GLUT_ELAPSED_TIME)
        elapsed = (thisTime - self.lastFrameTime) / 1000.0
        self.lastFrameTime = thisTime
        self.updateFPS = self.updateFPS - elapsed
 
        PyCEGUI.System.getSingleton().injectTimePulse(elapsed)
 
        # do rendering for this frame.
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        PyCEGUI.System.getSingleton().renderGUI()
        glutPostRedisplay()
        glutSwapBuffers()
 
    def reshapeFunc(self, width, height):
        glViewport(0, 0, width, height)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(60.0, width / height, 1.0, 50.0)
        glMatrixMode(GL_MODELVIEW)
        PyCEGUI.System.getSingleton().notifyDisplaySizeChanged(PyCEGUI.Size(width, height))
 
    def mouseFunc(self, button, state, x, y):
        if button == GLUT_LEFT_BUTTON:
            if state == GLUT_UP:
                PyCEGUI.System.getSingleton().injectMouseButtonUp(PyCEGUI.LeftButton)
            else:
                PyCEGUI.System.getSingleton().injectMouseButtonDown(PyCEGUI.LeftButton)
 
        elif button == GLUT_RIGHT_BUTTON:
            if state == GLUT_UP:
                PyCEGUI.System.getSingleton().injectMouseButtonUp(PyCEGUI.RightButton)
            else:
                PyCEGUI.System.getSingleton().injectMouseButtonDown(PyCEGUI.RightButton)
 
    def mouseMotionFunc(self, x, y):
        PyCEGUI.System.getSingleton().injectMousePosition(x, y)
 
app = BaseApp()
app.run()
del app

I must say that this app isn't perfect or complete but it will get you going. You might want to make it exception safe and add keyboard injection. If you do that, please post it back here for others to benefit.

Documentation

Since the API is resembling the C++ version as closely as possible (mainly to avoid confusion and as easy switch between C++ and Python as possible), doxygen API docs apply for most of the classes. Doxygen comments are also extracted and added to __doc__ of all the python classes and methods, using python docstrings is therefore possible but not perfect.

Subscriptions

One major thing that isn't documented in doxygen or other wiki docs and that is important in python is how subscriptions work.

Subscribing to listenerClass:

wnd.subscribeEvent(PyCEGUI.Window.EventMouseEnters, listenerClass, "methodInThatClass")

Subscribing to a free function:

wnd.subscribeEvent(PyCEGUI.Window.EventMouseEnters, PythonModule.freeFunction)

User Data

CEGUI has setUserData and getUserData methods in several classes. This is not exposed to Python at all. You can do something much much nicer and comfier in Python though:

a = PyCEGUI.ListboxTextItem("SSSS")
a.myUserData = "AAAAA" # myUserData can be anything, player, ship, whatever
print a.myUserData # prints AAAAA
 
# you can ofcourse have more of these user data custom attributes

These do NOT persist when you pass them to C++ and back, I have to figure out a way to make them persist :-/ --User:Kulik

Implementation

We use py++ and boost::python. It allows rapid development of the bindings and a very easy maintenance. It may be a bit slower than SWIG and definitely slower than hand written bindings but since both of these won't happen, please don't cry about that fact (unless you are willing to create and maintain those). The slowness is very unlikely to be noticeable at all unless you do synthetic tests.

Caveats

C++ allows you to shoot yourself in a lot of ways whilst python is very limited in this area. For example if you create a window, get it and store the reference in python variable, then destroy the window, the python variable will hold a dangling pointer. This is something you have to take care of unfortunately, there is no way for us to handle this unless we affect the C++ interface which would hinder performance for C++ users. It's not that hard to handle this and unless you do something really unusual, you probably aren't going to trigger it.

Panda3D integration

morgul has integrated CEGUI with Panda via these bindings - Pure python Panda CEGUI integration