Difference between revisions of "PyCEGUI/Example"

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Jump to: navigation, search
m (Directory hierarchy)
(Delete me.)
Line 1: Line 1:
== Introduction ==
+
Delete me.
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 ===
+
*/home/foo/bar/demo
+
**demo.py
+
**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 ===
+
<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.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):
+
        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
+
</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
+
    # - We will never actually get here.
+
    gfx.Shutdown()
+
    return 0
+
 
+
# Guard
+
if __name__ == '__main__':
+
    sys.exit(main())
+
</source>
+
 
+
=== demomenu.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 21:35, 18 June 2011

Delete me.