PyCEGUI/Example
Contents
Introduction
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.
Links of interest
Requirements
General
- 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
- /home/foo/bar/demo
- demo.py
- datafiles/
- datafiles/* (the base CEGUI datafiles)
- datafiles/layouts/demomenu.layout
- config/
- constants.py
- error/
- errors.py
- input/
- input.py
- video/
- video.py
- gui/
- gui.py
- demomenu.py
General notes
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.
In short: your mileage may vary.
Comments, improvements, etc
Use the discussion page.
Source
constants.py
#!/usr/bin/env python # -*- 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
errors.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # # errors.py """Errors. Currently defined errors include: InitializationError """ # Import: std import sys # Import: psyco try: import psyco psyco.full() except ImportError: pass # InitializationError class InitializationError(Exception): def __init__(self, msg): self.msg = msg def __str__(self): return ('InitializationError: %s' % self.msg)
video.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # # video.py """Video functionality. This class brings together PyCEGUI and OpenGL into a comfortable interface. """ # 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 from PyCEGUIOpenGLRenderer import OpenGLRenderer as Renderer # Import: User from constants import * from errors import InitializationError # Video class Video(object): # Initialize: OpenGL def initializeOpenGL(self): glutInit() 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
input.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # # input.py """Input functionality. 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
gui.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # # gui.py """GUI. 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.demoMenu.Setup() return # Setup: Interface def setupInterface(self): self.Setup() return
#!/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): self.GUISheet = PyCEGUI.System.getSingleton().getGUISheet() # 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/Button').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
demo.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # # demo.py """Demo entry point. 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 # - We will never actually get here. gfx.Shutdown() return 0 # Guard if __name__ == '__main__': sys.exit(main())
<?xml version="1.0" encoding="UTF-8"?> <GUILayout > <Window Type="TaharezLook/FrameWindow" Name="DemoMenu" > <Property Name="Font" Value="DejaVuSans-10" /> <Property Name="Text" Value="Demo 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/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>
Modifications
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:
def buttonClicked(self, args): editbox = self.menu.getChild('DemoMenu/Edit') print(editbox.getText()) return
Now, when the button is clicked, it will print to the console whatever has been typed in the box.
As an exercise to the reader, consider adding support for Backspace/Delete/Arrow keys (hint: Special) keys.