PyCEGUI/Example

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Revision as of 21:35, 18 June 2011 by Crond (Talk | contribs) (oops)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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/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

demomenu.py

#!/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())

demomenu.layout

<?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

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:

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.