Difference between revisions of "User:Crond/sandbox"
(created) |
(PyCEGUI/PyOpenGL: 1) |
||
Line 1: | Line 1: | ||
− | + | == 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 == | ||
+ | *[http://docs.python.org/release/2.6.5/ Python 2.6.5 documentation] | ||
+ | *[http://pyopengl.sourceforge.net/documentation/manual-3.0/index.xhtml PyOpenGL reference] | ||
+ | *[http://pyopengl.sourceforge.net/documentation/manual-3.0/index.xhtml#GLUT PyOpenGL GLUT reference] | ||
+ | |||
+ | == 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 === | ||
+ | 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 == | ||
+ | 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 === | ||
+ | <source lang="python"> | ||
+ | #!/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 | ||
+ | </source> | ||
+ | |||
+ | === errors.py === | ||
+ | <source lang="python"> | ||
+ | #!/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) | ||
+ | </source> | ||
+ | |||
+ | === video.py === | ||
+ | <source lang="python"> | ||
+ | #!/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 | ||
+ | </source> | ||
+ | |||
+ | === input.py === | ||
+ | <source lang="python"> | ||
+ | #!/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 | ||
+ | </source> | ||
+ | |||
+ | === gui.py === | ||
+ | <source lang="python"> | ||
+ | #!/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.GUISheet) | ||
+ | self.demoMenu.Setup() | ||
+ | return | ||
+ | |||
+ | # Setup: Interface | ||
+ | def setupInterface(self): | ||
+ | self.Setup() | ||
+ | return | ||
+ | </source> | ||
+ | |||
+ | === demomenu.py === | ||
+ | <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"> | ||
+ | #!/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 | ||
+ | gfx.Shutdown() | ||
+ | return 0 | ||
+ | |||
+ | # Guard | ||
+ | if __name__ == '__main__': | ||
+ | sys.exit(main()) | ||
+ | </source> | ||
+ | |||
+ | === mainmenu.layout === | ||
+ | <source lang="xml"> | ||
+ | <?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> | ||
+ | </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. | ||
+ | |||
+ | As an exercise to the reader, consider adding support for Backspace/Delete/Arrow keys (hint: Special) keys. |
Revision as of 19:27, 21 May 2011
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
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
- /home/foo/bar/demo/datafiles
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.GUISheet) 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, 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
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 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.