Difference between revisions of "User:Crond/sandbox"

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Jump to: navigation, search
(PyCEGUI/PyOpenGL: 1)
m (OpenGL driver bugs: someone elses problem)
 
(22 intermediate revisions by the same user not shown)
Line 1: Line 1:
== Introduction ==
+
{{VersionBadge|0.7}} {{VersionAtLeast|0.7.5}}
This is an example which creates a light, non-comprehensive framework for PyCEGUI, with the rendering system used being [Py]OpenGL. The reason OpenGL is used is because it's free, easy to get on any Ubuntu system (which this was written on), and portable. Also, it's the one demonstrated on the official [[PyCEGUI]] page.
+
<br /><br /><br />
 +
== History ==
 +
Since the release of CEGUI 0.7.5, official Python bindings have been provided, primarily prompted by the fact that the new CEGUI tools are written in Python. A beneficial side effect of this is that the bindings are free for anyone to use in their Python, or embedded Python, applications.
  
== Links of interest ==
+
== Downloads ==
*[http://docs.python.org/release/2.6.5/ Python 2.6.5 documentation]
+
* Windows: [http://pypi.python.org/pypi/PyCEGUI/0.7.5 PyPi repository]
*[http://pyopengl.sourceforge.net/documentation/manual-3.0/index.xhtml PyOpenGL reference]
+
* For other platforms, download the SDK and use the bindings contained within.
*[http://pyopengl.sourceforge.net/documentation/manual-3.0/index.xhtml#GLUT PyOpenGL GLUT reference]
+
* If you use embedded Python, it may be easier to use the Win32 bindings from the SDK.
  
== Requirements ==
+
== Documentation ==
=== General ===
+
The PyCEGUI API resembles the C++ version as closely as possible - mainly to avoid confusion and to provide familiarity between the two. The doxygen API docs (found [http://www.cegui.org.uk/docs/current here]) apply for most of the classes; additionally, doxygen comments are extracted and added as docstrings to all Python objects. This means that using Python docstrings is possible, although not perfect.
*Python 2.6
+
*PyCEGUI
+
*PyOpenGL, GLU, GLUT
+
=== Datafiles ===
+
This example assumes the CEGUI datafiles are in the same directory as `demo.py`; see the following section.
+
=== Directory hierarchy ===
+
Replace `/home/foo/bar` as necessary.
+
*/home/foo/bar/demo
+
**/home/foo/bar/demo/datafiles
+
***/home/foo/bar/demo/datafiles/layouts/DemoMenu.layout
+
**/home/foo/bar/demo/config
+
***/home/foo/bar/demo/config/constants.py
+
**/home/foo/bar/demo/error
+
***/home/foo/bar/demo/error/errors.py
+
**/home/foo/bar/demo/input
+
***/home/foo/bar/demo/input/input.py
+
**/home/foo/bar/demo/video
+
***/home/foo/bar/demo/video/video.py
+
**/home/foo/bar/demo/gui
+
***/home/foo/bar/demo/gui/gui.py
+
***/home/foo/bar/demo/gui/demomenu.py
+
**/home/foo/bar/demo/demo.py
+
  
== General notes ==
+
== Implementation ==
This is not intended to be fully comprehensive; rather, the intention is to illustrate '''one way''' of setting up a GUI system. A minimal amount of thought was given to "problems down the road;" which is to say that there is no guarantee that a particular design constraint will not render the framework unsuitable for a particular purpose.
+
PyCEGUI uses [http://freshmeat.net/projects/pyplusplus Py++] and [http://www.boost.org/doc/libs/1_46_1/libs/python/doc/ Boost.Python] - this allows rapid development of the bindings and easy maintenance. It may be a bit slower than [http://www.swig.org/ SWIG], and certainly slower than hand written bindings; but, since neither of those will happen, be content with what is provided. The slowness is very unlikely to be noticeable at all unless you do synthetic tests.
  
In short: your mileage may vary.
+
== Subscriptions ==
 +
One major thing that isn't documented in doxygen or other wiki docs and that is important is how subscriptions work in PyCEGUI.
  
== Comments, improvements, etc ==
+
Subscribing to listenerClass:
Use the discussion page.
+
 
+
== Source ==
+
=== constants.py ===
+
 
<source lang="python">
 
<source lang="python">
#!/usr/bin/env python
+
wnd.subscribeEvent(PyCEGUI.Window.EventMouseEnters, listenerClass, 'methodInThatClass')
# -*- coding: utf-8 -*-
+
#
+
# constants.py
+
 
+
 
+
"""Constant values.
+
 
+
Values currently contained in this file include:
+
 
+
    Version information
+
    Window name(s)
+
 
+
"""
+
 
+
# Import: std
+
import sys
+
 
+
# Import: psyco
+
try:
+
    import psyco
+
    psyco.full()
+
except ImportError:
+
    pass
+
 
+
 
+
# Versioning
+
MINOR_VERSION = '01'
+
MAJOR_VERSION = '0'
+
FULL_VERSION = '.'.join([MAJOR_VERSION, MINOR_VERSION])
+
 
+
# Name: Root window
+
NAME_ROOT_WINDOW = 'Demo: %s' % FULL_VERSION
+
 
</source>
 
</source>
  
=== errors.py ===
+
Subscribing to a free function:
 
<source lang="python">
 
<source lang="python">
#!/usr/bin/env python
+
wnd.subscribeEvent(PyCEGUI.Window.EventMouseEnters, PythonModule.freeFunction)
# -*- coding: utf-8 -*-
+
</source>
#
+
# errors.py
+
  
"""Errors.
+
=== Future release (0.8+) ===
 +
'''TODO: Resolve repetition.'''<br />
 +
In the future, the signatures have been changed to be much more flexible and Pythonic. 'subscribeEvent' now takes any callable object (bound member function, free function, lambda, functor, etc). Moving from the old syntax to the new syntax, the following
  
Currently defined errors include:
+
<source lang="python">
 +
wnd.subscribeEvent(PyCEGUI.Window.EventMouseEnters, instance, "someMethod")
 +
</source>
  
    InitializationError
+
becomes this
  
"""
+
<source lang="python">
 +
wnd.subscribeEvent(PyCEGUI.Window.EventMouseEnters, instance.someMethod)
 +
</source>
  
# Import: std
+
== Known issues ==
import sys
+
=== Reference counting ===
 +
'''Note: Fixed; pending release (0.8+)'''<br />
 +
C++, like C, gives the programmer a pretty free hand in allocating memory, passing it around, and possibly forgetting that it ever exists; while certainly not the recommended way of doing things, it is possible. Python, however, is a different creature altogether - by default, it has a garbage collection system which is implemented via reference counting (the specifics of this are not important, and left as an exercise to the reader if they are that interested). Basically, what this means, is that when an object is no longer needed, it is deleted.
  
# Import: psyco
+
This can play havoc with a C++ binding, if not considered carefully. For example, if a Python application has an object it wishes to let PyCEGUI (and, by association, C++ CEGUI) know about, it must take care to remember that Python is tracking the number of times the object in question has been referenced, but C++ is not. Thus, the conflict here is that Python will destroy (or garbage collect, if you prefer) the object in question, while C++ will be none the wiser; the inevitable conclusion here is an invalid pointer, segmentation fault, or other equally nasty bug.
try:
+
    import psyco
+
    psyco.full()
+
except ImportError:
+
    pass
+
  
 +
Let us illustrate just exactly what we are talking about here; consider the following:
  
# InitializationError
 
class InitializationError(Exception):
 
    def __init__(self, msg):
 
        self.msg = msg
 
    def __str__(self):
 
        return ('InitializationError: %s' % self.msg)
 
</source>
 
 
=== video.py ===
 
 
<source lang="python">
 
<source lang="python">
#!/usr/bin/env python
+
def outsideContext(argListbox):
# -*- coding: utf-8 -*-
+
  item1 = PyCEGUI.ListboxTextItem('item1')
#
+
  item2 = PyCEGUI.ListboxTextItem('item2')
# video.py
+
  argListbox.addItem(item1)
 +
  argListbox.addItem(item2)
 +
  return
  
 +
def main():
 +
  theListbox = PyCEGUI.WindowManager.getSingleton().createWindow('TaharezLook/Listbox', 'theListbox')
 +
  outsideContext(theListbox)
 +
  return
  
"""Video functionality.
+
if __name__ == '__main__':
 +
  sys.exit(main())
 +
</source>
  
This class brings together PyCEGUI and OpenGL into a comfortable interface.
+
In the function `outsideContext`, two items are created in that scope (and registered with the Python garbage collection system), and then added to a Listbox; when the function is finished, Python is under the impression that it no longer needs the items and so it destroys the objects. Under the hood, however, pointers have been arranged that refer to these two items; in the future, when the Listbox tries to manipulate the items or when the Listbox itself is garbage collected, segmentation faults will more than likely occur.
  
"""
+
One solution to this issue, if possible, is to bind the items to an object - preferably, the one which the Listbox is bound to, so that when the Listbox itself is no longer needed, the items will be cleaned up at the same time. Such a strategy might go as follows:
  
# Import: std
+
<source lang="python">
import sys
+
class someObject(object):
 +
  def __init__(self):
 +
    super(Object, self).__init__()
 +
    self.memory = []
 +
    return
 +
  def anInitializationMethod(self):
 +
    self.listbox = PyCEGUI.WindowManager.getSingleton().createWindow('TaharezLook/Listbox', 'someListbox')
 +
    return
 +
  def anotherMethod(self, stringList):
 +
    for s in stringList:
 +
      buffer = PyCEGUI.ListboxTextItem(s)
 +
      self.memory.append(buffer)
 +
      self.listbox.addItem(buffer)
 +
    return
 +
</source>
  
# Import: psyco
+
The key point here is to have Python keep the object alive until the Listbox in question is no longer needed; the implementation of it is irrelevant.
try:
+
    import psyco
+
    psyco.full()
+
except ImportError:
+
    pass
+
  
# Import: OpenGL
+
=== Dangling pointers ===
from OpenGL.GL import *
+
Another issue is almost the opposite of what was outlined previously - that is to say, PyCEGUI objects can be destroyed and Python will still think they are valid (in some sense, they are - just not functional). An example of this is creating a window, storing it in a variable, then destroying the window; a code snippet:
from OpenGL.GLU import *
+
from OpenGL.GLUT import *
+
  
# Import: PyCEGUI
+
<source lang="python">
import PyCEGUI
+
rootWindow = PyCEGUI.WindowManager.getSingleton().createWindow('DefaultWindow', 'root')
from PyCEGUIOpenGLRenderer import OpenGLRenderer as Renderer
+
PyCEGUI.System.getSingleton().setGUISheet(rootWindow)
  
# Import: User
+
someWindow = PyCEGUI.WindowManager.getSingleton().createWindow('TaharezLook/FrameWindow', 'someWindow')
from constants import *
+
rootWindow.addChildWindow(someWindow)
from errors import InitializationError
+
  
 +
# ... things happen ...
  
# Video
+
PyCEGUI.WindowManager.getSingleton().destroyWindow(someWindow)
class Video(object):
+
PyCEGUI.WindowManager.getSingleton().cleanDeadPool()
  
    # Initialize: OpenGL
+
# Death
    def initializeOpenGL(self):
+
# - Any functional method will crash the program; the choice of `disable` is totally arbitrary.
        glutInit()
+
someWindow.disable()
        glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA)
+
        glutInitWindowSize(1024, 768)
+
        glutInitWindowPosition(-1, -1)
+
        glutCreateWindow(NAME_ROOT_WINDOW)
+
        glutSetCursor(GLUT_CURSOR_NONE)
+
 
+
        # Handlers
+
        glutDisplayFunc(self.handlerDisplay)
+
        glutReshapeFunc(self.handlerReshape)
+
        return
+
 
+
    # Initialize: PyCEGUI
+
    def initializePyCEGUI(self):
+
        self.renderer = Renderer.bootstrapSystem()
+
        return
+
 
+
    # Initialize
+
    def Initialize(self):
+
        try:
+
            self.initializeOpenGL()
+
            self.initializePyCEGUI()
+
        except Exception, msg:
+
            raise InitializationError(msg)
+
        return
+
 
+
    # Shutdown
+
    # - For implicit use, use the Python special method `__del__`.
+
    def Shutdown(self):
+
        self.renderer.destroySystem()
+
        return
+
 
+
    # Handler: Display
+
    # - This is called to refresh the screen.
+
    # - See PyOpenGL documentation.
+
    def handlerDisplay(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)
+
 
+
        # Render this frame
+
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+
        PyCEGUI.System.getSingleton().renderGUI()
+
        glutPostRedisplay()
+
        glutSwapBuffers()
+
        return
+
 
+
    # Handler: Reshape
+
    # - This is called when the window is resized and/or switches to fullscreen.
+
    # - See PyOpenGL documentation.
+
    def handlerReshape(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))
+
        return
+
 
+
    # Main loop
+
    # - Set the initial values.
+
    # - This never returns; once this gets called, the application is driven entirely by events.
+
    def EnterMainLoop(self):
+
        self.lastFrameTime = glutGet(GLUT_ELAPSED_TIME)
+
        self.updateFPS = 0
+
        glutMainLoop()
+
        return
+
 
</source>
 
</source>
  
=== input.py ===
+
This is a contrived example, and most programs will have some form of logic that will prevent this, but it is worth noting.
<source lang="python">
+
#!/usr/bin/env python
+
# -*- coding: utf-8 -*-
+
#
+
# input.py
+
  
 +
== Panda3D integration ==
 +
morgul has integrated CEGUI with Panda via [http://www.panda3d.org/forums/viewtopic.php?t=10814 these bindings].
  
"""Input functionality.
+
== OpenGL driver bugs ==
 +
If an Intel 915GM, 910GML, or similar chipset is used, it might be necessary to create/destroy a window before the display mode is initialized via OpenGL. The following illustrates:
  
This class sets up input processing by creating handlers for GLUT that inject
 
input into PyCEGUI.
 
 
"""
 
 
# Import: std
 
import sys
 
 
# Import: psyco
 
try:
 
    import psyco
 
    psyco.full()
 
except ImportError:
 
    pass
 
 
# Import: OpenGL
 
from OpenGL.GL import *
 
from OpenGL.GLU import *
 
from OpenGL.GLUT import *
 
 
# Import: PyCEGUI
 
import PyCEGUI
 
 
# Import: User
 
from constants import *
 
from errors import InitializationError
 
 
 
# Input
 
class Input(object):
 
 
    # Initialize: Handlers
 
    def initializeHandlers(self):
 
        glutKeyboardFunc(self.handlerKeyDown)
 
        glutMouseFunc(self.handlerMouseButton)
 
 
        # The difference between these two is that the passive one is called when there is
 
        # mouse motion while no buttons are pressed, and the other is called when there
 
        # is mouse motion while buttons are pressed. See PyOpenGL documentation.
 
        glutMotionFunc(self.handlerMouseMotion)
 
        glutPassiveMotionFunc(self.handlerMouseMotion)
 
        return
 
 
    # Initialize
 
    def Initialize(self):
 
        try:
 
            self.initializeHandlers()
 
        except Exception, msg:
 
            raise InitializationError(msg)
 
        return
 
 
    # Handler: Key Down
 
    # - `ord` is a built-in Python function.
 
    def handlerKeyDown(self, key, x, y):
 
        PyCEGUI.System.getSingleton().injectChar(ord(key))
 
        return
 
 
    # Handler: Mouse Button
 
    def handlerMouseButton(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)
 
 
        # A thought is to turn this into an `else` clause; however, this implies that any
 
        # button besides the left is interpreted as the right button - this seems undesirable
 
        # for any mouse with more than two buttons.
 
        elif button == GLUT_RIGHT_BUTTON:
 
            if state == GLUT_UP:
 
                PyCEGUI.System.getSingleton().injectMouseButtonUp(PyCEGUI.RightButton)
 
            else:
 
                PyCEGUI.System.getSingleton().injectMouseButtonDown(PyCEGUI.RightButton)
 
 
        # An `else` clause could also go here to perform some arbitrary action on unhandled
 
        # mouse input; this is left as an exercise for the reader. Instead, we just implicitly
 
        # ignore it.
 
        return
 
 
    # Handler: Mouse Motion
 
    # - This might seem arbitrary, but in fact this is required or else the position of the mouse
 
    # will never be updated inside the window.
 
    def handlerMouseMotion(self, x, y):
 
        PyCEGUI.System.getSingleton().injectMousePosition(x, y)
 
        return
 
</source>
 
 
=== gui.py ===
 
 
<source lang="python">
 
<source lang="python">
#!/usr/bin/env python
+
glutInit()
# -*- coding: utf-8 -*-
+
#
+
# gui.py
+
  
 
+
# This will segfault the application
"""GUI.
+
glutInitDisplayMode(0)
 
+
This class is the entry point for the GUI; which is to say that it starts the
+
main menu and the rest is event driven.
+
 
+
"""
+
 
+
# Import: std
+
import sys
+
 
+
# Import: psyco
+
try:
+
    import psyco
+
    psyco.full()
+
except ImportError:
+
    pass
+
 
+
# Import: PyCEGUI
+
import PyCEGUI
+
 
+
# Import: User
+
from errors import InitializationError
+
import demomenu
+
 
+
 
+
# GUI
+
class GUI(object):
+
 
+
    # Initialize: Resources
+
    def initializeResources(self):
+
        rp = PyCEGUI.System.getSingleton().getResourceProvider()
+
        rp.setResourceGroupDirectory('schemes', './datafiles/schemes')
+
        rp.setResourceGroupDirectory('imagesets', './datafiles/imagesets')
+
        rp.setResourceGroupDirectory('fonts', './datafiles/fonts')
+
        rp.setResourceGroupDirectory('layouts', './datafiles/layouts')
+
        rp.setResourceGroupDirectory('looknfeels', './datafiles/looknfeel')
+
        rp.setResourceGroupDirectory('schemas', './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')
+
        return
+
 
+
    # Initialize: Defaults
+
    def initializeDefaults(self):
+
        sm = PyCEGUI.SchemeManager.getSingleton()
+
        sm.create('VanillaSkin.scheme')
+
        sm.create('TaharezLook.scheme')
+
        PyCEGUI.System.getSingleton().setDefaultMouseCursor('Vanilla-Images', 'MouseArrow')
+
        return
+
 
+
    # Initialize
+
    def Initialize(self):
+
        try:
+
            self.initializeResources()
+
            self.initializeDefaults()
+
 
+
            # GUISheet
+
            self.GUISheet = PyCEGUI.WindowManager.getSingleton().createWindow('DefaultWindow', 'Root')
+
            PyCEGUI.System.getSingleton().setGUISheet(self.GUISheet)
+
        except Exception, msg:
+
            raise InitializationError(msg)
+
        return
+
 
+
    # Setup
+
    # - Important: the instance of `demomenu.DemoMenu` has to be bound to this object; if it is
+
    # a local variable (read: destroyed when it goes out of scope), exceptions will be raised
+
    # about the `buttonClicked` method not existing. This is a drawback of the type of setup
+
    # this example uses, and as a consequence of Python being a garbage collected language.
+
    def Setup(self):
+
        self.demoMenu = demomenu.DemoMenu()
+
        self.demoMenu.Initialize(self.GUISheet)
+
        self.demoMenu.Setup()
+
        return
+
 
+
    # Setup: Interface
+
    def setupInterface(self):
+
        self.Setup()
+
        return
+
 
</source>
 
</source>
  
=== demomenu.py ===
+
Instead, the following can be done to circumvent this problem:
<source lang="python">
+
#!/usr/bin/env python
+
# -*- coding: utf-8 -*-
+
#
+
# demomenu.py
+
  
 
"""DemoMenu.
 
 
This class represents a demonstration of how to control a menu with PyCEGUI and PyOpenGL.
 
 
"""
 
 
# Import: std
 
import sys
 
 
# Import: psyco
 
try:
 
    import psyco
 
    psyco.full()
 
except ImportError:
 
    pass
 
 
# Import: PyCEGUI
 
import PyCEGUI
 
 
# Import: User
 
from errors import InitializationError
 
 
 
# DemoMenu
 
class DemoMenu(object):
 
 
    # Initialize
 
    def Initialize(self, guiSheet):
 
        self.GUISheet = guiSheet
 
 
        # Load the layout
 
        self.menu = PyCEGUI.WindowManager.getSingleton().loadWindowLayout('DemoMenu.layout')
 
        return
 
 
    # connectHandlers
 
    # - Wrapper method to define the subscription/listener relationships.
 
    # - If there are a lot, it may behoove the coder to encapsulate them in methods, then call those methods here.
 
    def connectHandlers(self):
 
        self.menu.getChild('DemoMenu/ButtonUsername').subscribeEvent(PyCEGUI.PushButton.EventClicked, self, 'buttonClicked')
 
        return
 
 
    # Setup
 
    def Setup(self):
 
 
        # Connect the handlers
 
        self.connectHandlers()
 
 
        # Attach
 
        self.GUISheet.addChildWindow(self.menu)
 
        return
 
 
    # Handler: buttonClicked
 
    def buttonClicked(self, args):
 
        print('buttonClicked')
 
        return
 
</source>
 
 
=== demo.py ===
 
 
<source lang="python">
 
<source lang="python">
#!/usr/bin/env python
+
glutInit()
# -*- coding: utf-8 -*-
+
#
+
# demo.py
+
  
 +
# Kludge
 +
glutDestroyWindow(glutCreateWindow(''))
  
"""Demo entry point.
+
# This will no longer segfault the application
 
+
glutInitDisplayMode(0)
A demonstration; mildly comprehensive, but not really.
+
 
+
"""
+
 
+
# Import: std
+
import sys, os.path
+
sys.path.append(os.path.join(os.getcwd(), 'config'))
+
sys.path.append(os.path.join(os.getcwd(), 'error'))
+
sys.path.append(os.path.join(os.getcwd(), 'input'))
+
sys.path.append(os.path.join(os.getcwd(), 'video'))
+
sys.path.append(os.path.join(os.getcwd(), 'gui'))
+
 
+
# Import: psyco
+
try:
+
    import psyco
+
    psyco.full()
+
except ImportError:
+
    pass
+
 
+
# Import: User
+
from errors import InitializationError
+
from input import Input
+
from video import Video
+
from gui import GUI
+
 
+
 
+
# Main
+
def main():
+
    gfx = Video()
+
    inp = Input()
+
    gui = GUI()
+
 
+
    # Initialize
+
    try:
+
        gfx.Initialize()
+
        inp.Initialize()
+
        gui.Initialize()
+
    except InitializationError as error:
+
        print(error)
+
        return 1
+
 
+
    # Setup the interface
+
    gui.setupInterface()
+
 
+
    # Main Loop
+
    gfx.EnterMainLoop()
+
 
+
    # Done
+
    gfx.Shutdown()
+
    return 0
+
 
+
# Guard
+
if __name__ == '__main__':
+
    sys.exit(main())
+
 
</source>
 
</source>
  
=== mainmenu.layout ===
+
Additional complications may arise via this chipset, which are outside the scope of [Py]CEGUI to deal with.
<source lang="xml">
+
<?xml version="1.0" encoding="UTF-8"?>
+
  
<GUILayout >
+
== Examples ==
    <Window Type="TaharezLook/FrameWindow" Name="DemoMenu" >
+
=== Basic OpenGL ===
        <Property Name="Font" Value="DejaVuSans-10" />
+
*A demonstration of a basic application using PyCEGUI and OpenGL can be found [[User:Crond/sandbox/openglExample|here]].
        <Property Name="Text" Value="Demo Menu" />
+
*A simple modification, which adds rudimentary keyboard support, can be found [[User:Crond/sandbox/openglKeyboardExample|here]].
        <Property Name="TitlebarFont" Value="DejaVuSans-10" />
+
*A further modification, which adds special keyboard support, can be found [[User:Crond/sandbox/openglSpecialKeyboardExample|here]].
        <Property Name="RollUpEnabled" Value="False" />
+
        <Property Name="TitlebarEnabled" Value="True" />
+
        <Property Name="UnifiedAreaRect" Value="{{0.157031,0},{0.194911,0},{0.783984,0},{0.687913,0}}" />
+
        <Property Name="DragMovingEnabled" Value="False" />
+
        <Property Name="CloseButtonEnabled" Value="False" />
+
        <Property Name="EWSizingCursorImage" Value="set:Vanilla-Images image:MouseArrow" />
+
        <Property Name="InheritsTooltipText" Value="False" />
+
        <Property Name="NSSizingCursorImage" Value="set:Vanilla-Images image:MouseArrow" />
+
        <Property Name="NESWSizingCursorImage" Value="set:Vanilla-Images image:MouseArrow" />
+
        <Property Name="NWSESizingCursorImage" Value="set:Vanilla-Images image:MouseArrow" />
+
        <Window Type="TaharezLook/StaticText" Name="DemoMenu/Text" >
+
            <Property Name="Text" Value="Enter some text" />
+
            <Property Name="UnifiedAreaRect" Value="{{0.0212363,0},{0.619781,0},{0.346946,0},{0.751932,0}}" />
+
        </Window>
+
        <Window Type="TaharezLook/Editbox" Name="DemoMenu/Edit" >
+
            <Property Name="MaxTextLength" Value="1073741823" />
+
            <Property Name="UnifiedAreaRect" Value="{{0.36704,0},{0.623671,0},{0.769671,0},{0.750419,0}}" />
+
            <Property Name="TextParsingEnabled" Value="False" />
+
        </Window>
+
        <Window Type="TaharezLook/Button" Name="DemoMenu/Button" >
+
            <Property Name="Text" Value="Click me" />
+
            <Property Name="UnifiedAreaRect" Value="{{0.78567,0},{0.620646,0},{0.979596,0},{0.749355,0}}" />
+
        </Window>
+
    </Window>
+
</GUILayout>
+
</source>
+
 
+
== Modifications ==
+
=== demomenu.py ===
+
To further demonstrate usage of how to manipulate the widgets and branch the script/code divide, consider the following modification to the `demomenu.DemoMenu.buttonClicked` method:
+
 
+
<source lang="python">
+
def buttonClicked(self, args):
+
    editbox = self.menu.getChild('DemoMenu/Edit')
+
    print(editbox.getText())
+
    return
+
</source>
+
  
Now, when the button is clicked, it will print to the console whatever has been typed in the box.
+
=== Subscriptions ===
 +
* A basic demonstration of subscribing to events can be found [[User:Crond/sandbox/openglEventExample|here]].
 +
* A further demonstration of event subscriptions can be found [[User:Crond/sandbox/openglEventExample2|here]].
  
As an exercise to the reader, consider adding support for Backspace/Delete/Arrow keys (hint: Special) keys.
+
=== Application from scratch ===
 +
* See [[User:Crond/sandbox/application1|here]].

Latest revision as of 15:22, 10 July 2011

Written for CEGUI 0.7


Works with versions 0.7.x (obsolete)

Requires at least version
0.7.5




History

Since the release of CEGUI 0.7.5, official Python bindings have been provided, primarily prompted by the fact that the new CEGUI tools are written in Python. A beneficial side effect of this is that the bindings are free for anyone to use in their Python, or embedded Python, applications.

Downloads

  • Windows: PyPi repository
  • For other platforms, download the SDK and use the bindings contained within.
  • If you use embedded Python, it may be easier to use the Win32 bindings from the SDK.

Documentation

The PyCEGUI API resembles the C++ version as closely as possible - mainly to avoid confusion and to provide familiarity between the two. The doxygen API docs (found here) apply for most of the classes; additionally, doxygen comments are extracted and added as docstrings to all Python objects. This means that using Python docstrings is possible, although not perfect.

Implementation

PyCEGUI uses Py++ and Boost.Python - this allows rapid development of the bindings and easy maintenance. It may be a bit slower than SWIG, and certainly slower than hand written bindings; but, since neither of those will happen, be content with what is provided. The slowness is very unlikely to be noticeable at all unless you do synthetic tests.

Subscriptions

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

Subscribing to listenerClass:

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

Subscribing to a free function:

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

Future release (0.8+)

TODO: Resolve repetition.
In the future, the signatures have been changed to be much more flexible and Pythonic. 'subscribeEvent' now takes any callable object (bound member function, free function, lambda, functor, etc). Moving from the old syntax to the new syntax, the following

wnd.subscribeEvent(PyCEGUI.Window.EventMouseEnters, instance, "someMethod")

becomes this

wnd.subscribeEvent(PyCEGUI.Window.EventMouseEnters, instance.someMethod)

Known issues

Reference counting

Note: Fixed; pending release (0.8+)
C++, like C, gives the programmer a pretty free hand in allocating memory, passing it around, and possibly forgetting that it ever exists; while certainly not the recommended way of doing things, it is possible. Python, however, is a different creature altogether - by default, it has a garbage collection system which is implemented via reference counting (the specifics of this are not important, and left as an exercise to the reader if they are that interested). Basically, what this means, is that when an object is no longer needed, it is deleted.

This can play havoc with a C++ binding, if not considered carefully. For example, if a Python application has an object it wishes to let PyCEGUI (and, by association, C++ CEGUI) know about, it must take care to remember that Python is tracking the number of times the object in question has been referenced, but C++ is not. Thus, the conflict here is that Python will destroy (or garbage collect, if you prefer) the object in question, while C++ will be none the wiser; the inevitable conclusion here is an invalid pointer, segmentation fault, or other equally nasty bug.

Let us illustrate just exactly what we are talking about here; consider the following:

def outsideContext(argListbox):
  item1 = PyCEGUI.ListboxTextItem('item1')
  item2 = PyCEGUI.ListboxTextItem('item2')
  argListbox.addItem(item1)
  argListbox.addItem(item2)
  return
 
def main():
  theListbox = PyCEGUI.WindowManager.getSingleton().createWindow('TaharezLook/Listbox', 'theListbox')
  outsideContext(theListbox)
  return
 
if __name__ == '__main__':
  sys.exit(main())

In the function `outsideContext`, two items are created in that scope (and registered with the Python garbage collection system), and then added to a Listbox; when the function is finished, Python is under the impression that it no longer needs the items and so it destroys the objects. Under the hood, however, pointers have been arranged that refer to these two items; in the future, when the Listbox tries to manipulate the items or when the Listbox itself is garbage collected, segmentation faults will more than likely occur.

One solution to this issue, if possible, is to bind the items to an object - preferably, the one which the Listbox is bound to, so that when the Listbox itself is no longer needed, the items will be cleaned up at the same time. Such a strategy might go as follows:

class someObject(object):
  def __init__(self):
    super(Object, self).__init__()
    self.memory = []
    return
  def anInitializationMethod(self):
    self.listbox = PyCEGUI.WindowManager.getSingleton().createWindow('TaharezLook/Listbox', 'someListbox')
    return
  def anotherMethod(self, stringList):
    for s in stringList:
      buffer = PyCEGUI.ListboxTextItem(s)
      self.memory.append(buffer)
      self.listbox.addItem(buffer)
    return

The key point here is to have Python keep the object alive until the Listbox in question is no longer needed; the implementation of it is irrelevant.

Dangling pointers

Another issue is almost the opposite of what was outlined previously - that is to say, PyCEGUI objects can be destroyed and Python will still think they are valid (in some sense, they are - just not functional). An example of this is creating a window, storing it in a variable, then destroying the window; a code snippet:

rootWindow = PyCEGUI.WindowManager.getSingleton().createWindow('DefaultWindow', 'root')
PyCEGUI.System.getSingleton().setGUISheet(rootWindow)
 
someWindow = PyCEGUI.WindowManager.getSingleton().createWindow('TaharezLook/FrameWindow', 'someWindow')
rootWindow.addChildWindow(someWindow)
 
# ... things happen ...
 
PyCEGUI.WindowManager.getSingleton().destroyWindow(someWindow)
PyCEGUI.WindowManager.getSingleton().cleanDeadPool()
 
# Death
# - Any functional method will crash the program; the choice of `disable` is totally arbitrary.
someWindow.disable()

This is a contrived example, and most programs will have some form of logic that will prevent this, but it is worth noting.

Panda3D integration

morgul has integrated CEGUI with Panda via these bindings.

OpenGL driver bugs

If an Intel 915GM, 910GML, or similar chipset is used, it might be necessary to create/destroy a window before the display mode is initialized via OpenGL. The following illustrates:

glutInit()
 
# This will segfault the application
glutInitDisplayMode(0)

Instead, the following can be done to circumvent this problem:

glutInit()
 
# Kludge
glutDestroyWindow(glutCreateWindow(''))
 
# This will no longer segfault the application
glutInitDisplayMode(0)

Additional complications may arise via this chipset, which are outside the scope of [Py]CEGUI to deal with.

Examples

Basic OpenGL

  • A demonstration of a basic application using PyCEGUI and OpenGL can be found here.
  • A simple modification, which adds rudimentary keyboard support, can be found here.
  • A further modification, which adds special keyboard support, can be found here.

Subscriptions

  • A basic demonstration of subscribing to events can be found here.
  • A further demonstration of event subscriptions can be found here.

Application from scratch