Difference between revisions of "User:Crond/sandbox"
(blanked) |
m (1) |
||
Line 1: | Line 1: | ||
+ | {{VersionBadge|0.7}} {{VersionAtLeast|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: [http://pypi.python.org/pypi/PyCEGUI/0.7.5 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. | ||
+ | |||
+ | == Known issues == | ||
+ | === User data === | ||
+ | C++ CEGUI has accessor methods `[get/set]UserData` in several classes, which are not exposed to Python. The reason for this is because of the mutable nature of most Python objects: that is to say, the majority of Python objects can have attributes defined or deleted on the fly. Consider the following: | ||
+ | |||
+ | <source lang="python"> | ||
+ | someObject = PyCEGUI.Listbox('someType', 'someName') | ||
+ | someObject.randomAttribute = 'Toga! Toga!' | ||
+ | print(someObject.randomAttribute) | ||
+ | </source> | ||
+ | |||
+ | It should be obvious, here, why `[...]UserData` functions from CEGUI are not necessary. In the happenstance that it is not, let us be very clear: any application can define a new attribute (or method, etc) on any mutable Python object, and the same rules apply to PyCEGUI objects, thus rendering "user data" methods superfluous. | ||
+ | |||
+ | === Caveat: Persistence === | ||
+ | For the folks who are exploring both CEGUI and PyCEGUI, consider that attributes and methods defined in Python do not persist when passed back into C++; that is to say, if an attribute is defined on some arbitrary PyCEGUI object, and that object is passed (indirectly) into C++, there should be no expectation that the aforementioned attribute will exist. | ||
+ | |||
+ | === Reference counting === | ||
+ | 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: | ||
+ | |||
+ | <source lang="python"> | ||
+ | pass | ||
+ | </source> | ||
+ | |||
+ | |||
+ | ... todo ... | ||
+ | |||
+ | == 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. | ||
+ | |||
+ | === Listbox === | ||
+ | This is of particular concern when populating a listbox widget from Python; consider the following code: | ||
+ | |||
+ | <source lang="python"> | ||
+ | def someMethod(self): | ||
+ | listBox = someWidget.getChild('aListBox') | ||
+ | item = PyCEGUI.ListboxTextItem('Some text') | ||
+ | listBox.addItem(item) | ||
+ | return | ||
+ | </source> | ||
+ | |||
+ | This code will produce a segmentation fault because when the local variable `item` goes out of scope, Python will garbage collect it - more specifically, the listbox will reference a listbox item which no longer exists. A possible solution to this problem is to bind it to an object, such as the following: | ||
+ | |||
+ | <source lang="python"> | ||
+ | def someMethod(self): | ||
+ | listBox = someWidget.getChild('aListBox') | ||
+ | self.item = PyCEGUI.ListboxTextItem('Some text') | ||
+ | listBox.addItem(self.item) | ||
+ | return | ||
+ | </source> | ||
+ | |||
+ | The key difference here is that the `ListboxTextItem` is bound to the instance of the class, thus the implication is that as long as the instance exists, so will the listbox item; they will both be destroyed when the instance in question goes out of scope. | ||
+ | |||
+ | This is a somewhat contrived example, and in all likelihood a practical implication of this would be more complicated. | ||
+ | |||
+ | == Panda3D integration == | ||
+ | morgul has integrated CEGUI with Panda via these bindings - [http://www.panda3d.org/forums/viewtopic.php?t=10814 Pure python Panda CEGUI integration] | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | == 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. | ||
+ | |||
+ | Also, note that this script assumes that the default CEGUI resources (lua_scripts, schemes, xml_schemas, etc) are located in a directory named 'datafiles', which itself is located in the same directory as the script itself. That is to say, if the script path is '/home/foo/script.py', there needs to be a path '/home/foo/datafiles' which contains all the resources. | ||
+ | |||
+ | <source lang="python"> | ||
+ | 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 | ||
+ | </source> | ||
+ | |||
+ | 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: | ||
+ | <source lang="python"> | ||
+ | wnd.subscribeEvent(PyCEGUI.Window.EventMouseEnters, listenerClass, "methodInThatClass") | ||
+ | </source> | ||
+ | |||
+ | Subscribing to a free function: | ||
+ | <source lang="python"> | ||
+ | wnd.subscribeEvent(PyCEGUI.Window.EventMouseEnters, PythonModule.freeFunction) | ||
+ | </source> | ||
+ | |||
+ | === Example === | ||
+ | An example of this, using a single class for simplicity, might go as follows. | ||
+ | |||
+ | <source lang="python"> | ||
+ | class SingleClassGUI: | ||
+ | |||
+ | # Initialization things | ||
+ | # - Create/set the GUISheet. | ||
+ | def __init__(self): | ||
+ | self.GUISheet = PyCEGUI.WindowManager.getSingleton().createWindow('DefaultWindow', 'root') | ||
+ | PyCEGUI.System.getSingleton().setGUISheet(self.GUISheet) | ||
+ | return | ||
+ | |||
+ | # Setup a window with a button to demonstrate subscriptions | ||
+ | def setupMenu(self): | ||
+ | menu = PyCEGUI.WindowManager.getSingleton().loadWindowLayout('menu.layout') # See below for 'menu.layout' | ||
+ | |||
+ | # This defines the widget we are accessing (in this case, the widget with path 'Menu/Button') ... | ||
+ | # the event we are listening for (in this case, the clicking of a push button) ... | ||
+ | # and the method we want to call when the aforementioned event takes place. | ||
+ | # | ||
+ | # Important note: the second argument to `subscribeEvent` is the class which should be used to find the | ||
+ | # method, as indicated by the third argument. Because this is a single class example, we just use `self`. | ||
+ | # If, however, this was a more practical example, a different class would be passed in, which would be | ||
+ | # investigated for the proper method to call. | ||
+ | menu.getChild('Menu/Button').subscribeEvent(PyCEGUI.PushButton.EventClicked, self, 'buttonClicked') | ||
+ | |||
+ | # Or, it could be written like this, which is more verbose but functionally equivalent | ||
+ | #button = menu.getChild('Menu/Button') | ||
+ | #button.subscribeEvent(PyCEGUI.PushButton.EventClicked, self, 'buttonClicked') | ||
+ | |||
+ | # Don't forget to do this | ||
+ | self.GUISheet.addChildWindow(menu) | ||
+ | return | ||
+ | |||
+ | # Handler | ||
+ | # - `args` is context sensitive; which is to say that its value depends on the type of event. See the EventArgs class for details. | ||
+ | def buttonClicked(self, args): | ||
+ | print('buttonClicked') | ||
+ | return | ||
+ | </source> | ||
+ | |||
+ | This is the XML layout file which defines how the window frame will appear. There is no doubt more information than necessary here for the example, but the main points (from a coding point of view) to pay attention to are the properties which define the name of the menu, and the button. | ||
+ | <source lang="xml"> | ||
+ | <?xml version="1.0" encoding="UTF-8"?> | ||
+ | |||
+ | <GUILayout > | ||
+ | <Window Type="TaharezLook/FrameWindow" Name="Menu" > | ||
+ | <Property Name="Font" Value="DejaVuSans-10" /> | ||
+ | <Property Name="Text" Value="An example Menu" /> | ||
+ | <Property Name="TitlebarFont" Value="DejaVuSans-10" /> | ||
+ | <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/Button" Name="Menu/Button" > | ||
+ | <Property Name="Text" Value="Button" /> | ||
+ | <Property Name="UnifiedAreaRect" Value="{{0.78567,0},{0.620646,0},{0.979596,0},{0.749355,0}}" /> | ||
+ | </Window> | ||
+ | </Window> | ||
+ | </GUILayout> | ||
+ | </source> | ||
+ | |||
+ | The fourth line defines the type of window, and the name of it - this is the name used to access it (and its children) from Python. | ||
+ | |||
+ | Farther down (the next <Window></Window> block) is the line where the button is defined; first there is the type of button, and then the name of it - in this case, 'Menu/Button'; note that this is the name (or path, if you prefer) we use to access it from Python (see the SingleClassGUI.setupMenu method above). | ||
+ | |||
+ | On a final note, this example assumes that some renderer has been setup - via OpenGL, Ogre, etc. Out of the box, this example will '''not''' work, because [Py]CEGUI has no renderer setup; once a system has been bootstrapped, this example will display a frame with a button, which when clicked, will print 'buttonClicked' to the console. | ||
+ | |||
+ | Something akin to the following: | ||
+ | |||
+ | <source lang="python"> | ||
+ | # Renderer setup goes here | ||
+ | pass | ||
+ | |||
+ | # Make it so. | ||
+ | gui = SingleClassGUI() | ||
+ | gui.setupMenu() | ||
+ | |||
+ | # Enter rendering loop goes here | ||
+ | </source> | ||
+ | |||
+ | === Multi-class example === | ||
+ | See [[PyCEGUI/Example|here]]. | ||
+ | |||
+ | [[Category:Manuals]] |
Revision as of 20:41, 28 May 2011
Written for CEGUI 0.7
Works with versions 0.7.x (obsolete)
Requires at least version
0.7.5
Contents
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.
Known issues
User data
C++ CEGUI has accessor methods `[get/set]UserData` in several classes, which are not exposed to Python. The reason for this is because of the mutable nature of most Python objects: that is to say, the majority of Python objects can have attributes defined or deleted on the fly. Consider the following:
someObject = PyCEGUI.Listbox('someType', 'someName') someObject.randomAttribute = 'Toga! Toga!' print(someObject.randomAttribute)
It should be obvious, here, why `[...]UserData` functions from CEGUI are not necessary. In the happenstance that it is not, let us be very clear: any application can define a new attribute (or method, etc) on any mutable Python object, and the same rules apply to PyCEGUI objects, thus rendering "user data" methods superfluous.
Caveat: Persistence
For the folks who are exploring both CEGUI and PyCEGUI, consider that attributes and methods defined in Python do not persist when passed back into C++; that is to say, if an attribute is defined on some arbitrary PyCEGUI object, and that object is passed (indirectly) into C++, there should be no expectation that the aforementioned attribute will exist.
Reference counting
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:
pass
... todo ...
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.
Listbox
This is of particular concern when populating a listbox widget from Python; consider the following code:
def someMethod(self): listBox = someWidget.getChild('aListBox') item = PyCEGUI.ListboxTextItem('Some text') listBox.addItem(item) return
This code will produce a segmentation fault because when the local variable `item` goes out of scope, Python will garbage collect it - more specifically, the listbox will reference a listbox item which no longer exists. A possible solution to this problem is to bind it to an object, such as the following:
def someMethod(self): listBox = someWidget.getChild('aListBox') self.item = PyCEGUI.ListboxTextItem('Some text') listBox.addItem(self.item) return
The key difference here is that the `ListboxTextItem` is bound to the instance of the class, thus the implication is that as long as the instance exists, so will the listbox item; they will both be destroyed when the instance in question goes out of scope.
This is a somewhat contrived example, and in all likelihood a practical implication of this would be more complicated.
Panda3D integration
morgul has integrated CEGUI with Panda via these bindings - Pure python Panda CEGUI integration
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.
Also, note that this script assumes that the default CEGUI resources (lua_scripts, schemes, xml_schemas, etc) are located in a directory named 'datafiles', which itself is located in the same directory as the script itself. That is to say, if the script path is '/home/foo/script.py', there needs to be a path '/home/foo/datafiles' which contains all the resources.
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)
Example
An example of this, using a single class for simplicity, might go as follows.
class SingleClassGUI: # Initialization things # - Create/set the GUISheet. def __init__(self): self.GUISheet = PyCEGUI.WindowManager.getSingleton().createWindow('DefaultWindow', 'root') PyCEGUI.System.getSingleton().setGUISheet(self.GUISheet) return # Setup a window with a button to demonstrate subscriptions def setupMenu(self): menu = PyCEGUI.WindowManager.getSingleton().loadWindowLayout('menu.layout') # See below for 'menu.layout' # This defines the widget we are accessing (in this case, the widget with path 'Menu/Button') ... # the event we are listening for (in this case, the clicking of a push button) ... # and the method we want to call when the aforementioned event takes place. # # Important note: the second argument to `subscribeEvent` is the class which should be used to find the # method, as indicated by the third argument. Because this is a single class example, we just use `self`. # If, however, this was a more practical example, a different class would be passed in, which would be # investigated for the proper method to call. menu.getChild('Menu/Button').subscribeEvent(PyCEGUI.PushButton.EventClicked, self, 'buttonClicked') # Or, it could be written like this, which is more verbose but functionally equivalent #button = menu.getChild('Menu/Button') #button.subscribeEvent(PyCEGUI.PushButton.EventClicked, self, 'buttonClicked') # Don't forget to do this self.GUISheet.addChildWindow(menu) return # Handler # - `args` is context sensitive; which is to say that its value depends on the type of event. See the EventArgs class for details. def buttonClicked(self, args): print('buttonClicked') return
This is the XML layout file which defines how the window frame will appear. There is no doubt more information than necessary here for the example, but the main points (from a coding point of view) to pay attention to are the properties which define the name of the menu, and the button.
<?xml version="1.0" encoding="UTF-8"?> <GUILayout > <Window Type="TaharezLook/FrameWindow" Name="Menu" > <Property Name="Font" Value="DejaVuSans-10" /> <Property Name="Text" Value="An example Menu" /> <Property Name="TitlebarFont" Value="DejaVuSans-10" /> <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/Button" Name="Menu/Button" > <Property Name="Text" Value="Button" /> <Property Name="UnifiedAreaRect" Value="{{0.78567,0},{0.620646,0},{0.979596,0},{0.749355,0}}" /> </Window> </Window> </GUILayout>
The fourth line defines the type of window, and the name of it - this is the name used to access it (and its children) from Python.
Farther down (the next <Window></Window> block) is the line where the button is defined; first there is the type of button, and then the name of it - in this case, 'Menu/Button'; note that this is the name (or path, if you prefer) we use to access it from Python (see the SingleClassGUI.setupMenu method above).
On a final note, this example assumes that some renderer has been setup - via OpenGL, Ogre, etc. Out of the box, this example will not work, because [Py]CEGUI has no renderer setup; once a system has been bootstrapped, this example will display a frame with a button, which when clicked, will print 'buttonClicked' to the console.
Something akin to the following:
# Renderer setup goes here pass # Make it so. gui = SingleClassGUI() gui.setupMenu() # Enter rendering loop goes here
Multi-class example
See here.