[Beta] CEGUI 0.7.5 in Python

Ever since moving to Panda3D, I’ve missed the nice GUI I had working with CEGUI. I’ve yet to find a good alternative, and the solutions here on the forums have always been works in progress, and involved a bit more than ‘drop this python file into you code and go.’

On a whim, I poked the CEGUI site and saw that they now have PyCEGUI, officially supported python bindings. Well, I decided it was time to get CEGUI and Panda working together.

I’m working on a very simplistic sample script that has mouse + keyboard input and CEGUI rendering.

Update: I’ve turned this into a module for CEGUI integration. I’m providing the code here as well as a test script for using it. I’m trying to take as much of the pain out of integrating CEGUI with panda as possible.

Update 2: I consider this module to be in Beta status.

Screenshot:


pcegui.py:

# -*- coding: utf-8 -*-
"""Panda/PyCEGUI Integration Module

Copyright (c) 2011 Christopher S. Case
Licensed under the MIT license; see the LICENSE file for details.

"""
# Python imports 
import os, sys, string 

# PyCEGUI imports 
import PyCEGUI 
import PyCEGUIOpenGLRenderer 

# Panda imports 
from panda3d.core import PythonCallbackObject, CallbackNode, DataNode 
from pandac.PandaModules import WindowProperties, ModifierButtons 

class PandaCEGUI(object): 
    """A python module for integrating PyCEGUI with Panda3D. 

    """ 
    buttons = { 
        'mouse1': PyCEGUI.MouseButton.LeftButton, 
        'mouse2': PyCEGUI.MouseButton.RightButton, 
        'mouse3': PyCEGUI.MouseButton.MiddleButton, 
        'mouse1-up': PyCEGUI.MouseButton.LeftButton, 
        'mouse2-up': PyCEGUI.MouseButton.RightButton, 
        'mouse3-up': PyCEGUI.MouseButton.MiddleButton, 
        'wheel_up': PyCEGUI.MouseButton.NoButton, 
        'wheel_down': PyCEGUI.MouseButton.NoButton, 
    } 
    
    keys = { 
        'a': (PyCEGUI.Key.Scan.A, 'a', 'A'), 
        'b': (PyCEGUI.Key.Scan.B, 'b', 'B'), 
        'c': (PyCEGUI.Key.Scan.C, 'c', 'C'), 
        'd': (PyCEGUI.Key.Scan.D, 'd', 'D'), 
        'e': (PyCEGUI.Key.Scan.E, 'e', 'E'), 
        'f': (PyCEGUI.Key.Scan.F, 'f', 'F'), 
        'g': (PyCEGUI.Key.Scan.G, 'g', 'G'), 
        'h': (PyCEGUI.Key.Scan.H, 'h', 'H'), 
        'i': (PyCEGUI.Key.Scan.I, 'i', 'I'), 
        'j': (PyCEGUI.Key.Scan.J, 'j', 'J'), 
        'k': (PyCEGUI.Key.Scan.K, 'k', 'K'), 
        'l': (PyCEGUI.Key.Scan.L, 'l', 'L'), 
        'm': (PyCEGUI.Key.Scan.M, 'm', 'M'), 
        'n': (PyCEGUI.Key.Scan.N, 'n', 'N'), 
        'o': (PyCEGUI.Key.Scan.O, 'o', 'O'), 
        'p': (PyCEGUI.Key.Scan.P, 'p', 'P'), 
        'q': (PyCEGUI.Key.Scan.Q, 'q', 'Q'), 
        'r': (PyCEGUI.Key.Scan.R, 'r', 'R'), 
        's': (PyCEGUI.Key.Scan.S, 's', 'S'), 
        't': (PyCEGUI.Key.Scan.T, 't', 'T'), 
        'u': (PyCEGUI.Key.Scan.U, 'u', 'U'), 
        'v': (PyCEGUI.Key.Scan.V, 'v', 'V'), 
        'w': (PyCEGUI.Key.Scan.W, 'w', 'W'), 
        'x': (PyCEGUI.Key.Scan.X, 'x', 'X'), 
        'y': (PyCEGUI.Key.Scan.Y, 'y', 'Y'), 
        'z': (PyCEGUI.Key.Scan.Z, 'z', 'Z'), 

        '`': (PyCEGUI.Key.Scan.Grave, '`', '~'), 
        '0': (PyCEGUI.Key.Scan.Zero, '0', ')'), 
        '1': (PyCEGUI.Key.Scan.One, '1', '!'), 
        '2': (PyCEGUI.Key.Scan.Two, '2', '@'), 
        '3': (PyCEGUI.Key.Scan.Three, '3', '#'), 
        '4': (PyCEGUI.Key.Scan.Four, '4', '$'), 
        '5': (PyCEGUI.Key.Scan.Five, '5', '%'), 
        '6': (PyCEGUI.Key.Scan.Six, '6', '^'), 
        '7': (PyCEGUI.Key.Scan.Seven, '7', '&'), 
        '8': (PyCEGUI.Key.Scan.Eight, '8', '*'), 
        '9': (PyCEGUI.Key.Scan.Nine, '9', '('), 
        '-': (PyCEGUI.Key.Scan.Minus, '-', '_'), 
        '=': (PyCEGUI.Key.Scan.Equals, '=', '+'), 


        '[': (PyCEGUI.Key.Scan.LeftBracket, '[', '{'), 
        ']': (PyCEGUI.Key.Scan.RightBracket, ']', '}'), 
        '\\': (PyCEGUI.Key.Scan.Backslash, '\\', '|'), 
        ';': (PyCEGUI.Key.Scan.Semicolon, ';', ':'), 

        "'": (PyCEGUI.Key.Scan.Apostrophe, "'", '"'), 
        ',': (PyCEGUI.Key.Scan.Comma, ',', '<'), 
        '.': (PyCEGUI.Key.Scan.Period, '.', '>'), 
        '/': (PyCEGUI.Key.Scan.Slash, '/', '?'), 

        'f1': (PyCEGUI.Key.Scan.F1, '', ''), 
        'f2': (PyCEGUI.Key.Scan.F3, '', ''), 
        'f3': (PyCEGUI.Key.Scan.F3, '', ''), 
        'f4': (PyCEGUI.Key.Scan.F4, '', ''), 
        'f5': (PyCEGUI.Key.Scan.F5, '', ''), 
        'f6': (PyCEGUI.Key.Scan.F6, '', ''), 
        'f7': (PyCEGUI.Key.Scan.F7, '', ''), 
        'f8': (PyCEGUI.Key.Scan.F8, '', ''), 
        'f9': (PyCEGUI.Key.Scan.F9, '', ''), 
        'f10': (PyCEGUI.Key.Scan.F10, '', ''), 
        'f11': (PyCEGUI.Key.Scan.F11, '', ''), 
        'f12': (PyCEGUI.Key.Scan.F12, '', ''), 

        'enter': (PyCEGUI.Key.Scan.Return, '\r', '\r'), 
        'tab': (PyCEGUI.Key.Scan.Tab, '\t', '\t'), 
        'space': (PyCEGUI.Key.Scan.Space, ' ', ' '), 

        'escape': (PyCEGUI.Key.Scan.Escape, '', ''), 
        'backspace': (PyCEGUI.Key.Scan.Backspace, '', ''), 

        'insert': (PyCEGUI.Key.Scan.Insert, '', ''), 
        'delete': (PyCEGUI.Key.Scan.Delete, '', ''), 

        'home': (PyCEGUI.Key.Scan.Home, '', ''), 
        'end': (PyCEGUI.Key.Scan.End, '', ''), 
        'page_up': (PyCEGUI.Key.Scan.PageUp, '', ''), 
        'page_down': (PyCEGUI.Key.Scan.PageDown, '', ''), 

        'arrow_left': (PyCEGUI.Key.Scan.ArrowLeft, '', ''), 
        'arrow_up': (PyCEGUI.Key.Scan.ArrowUp, '', ''), 
        'arrow_down': (PyCEGUI.Key.Scan.ArrowDown, '', ''), 
        'arrow_right': (PyCEGUI.Key.Scan.ArrowRight, '', ''), 

        'num_lock': (PyCEGUI.Key.Scan.NumLock, '', ''), 
        'caps_lock': (PyCEGUI.Key.Scan.Capital, '', ''), 
        'scroll_lock': (PyCEGUI.Key.Scan.ScrollLock, '', ''), 

        'lshift': (PyCEGUI.Key.Scan.LeftShift, '', ''), 
        'rshift': (PyCEGUI.Key.Scan.RightShift, '', ''), 
        'lcontrol': (PyCEGUI.Key.Scan.LeftControl, '', ''), 
        'rcontrol': (PyCEGUI.Key.Scan.RightControl, '', ''), 
        'lalt': (PyCEGUI.Key.Scan.LeftAlt, '', ''), 
        'ralt': (PyCEGUI.Key.Scan.RightAlt, '', ''), 
        } 

    # Tells PandaCEGUI to handle hiding/showing the system cursor on enable/disable 
    hideSystemCursor = True 
    _renderingEnabled = True 
    _capsLock = False 
    _shiftCount = 0 

    def __init__(self): 
        # Panda Setup 
        ceguiCB = PythonCallbackObject(self.renderCallback) 
        self.cbNode = CallbackNode("CEGUI") 
        self.cbNode.setDrawCallback(ceguiCB) 
        render2d.attachNewNode(self.cbNode) 
        
        base.accept('window-event', self.windowEvent)

        # Initialize the OpenGLRenderer 
        PyCEGUIOpenGLRenderer.OpenGLRenderer.bootstrapSystem() 

        self.props = WindowProperties() 

        # For convienence 
        self.System = PyCEGUI.System.getSingleton() 
        self.WindowManager = PyCEGUI.WindowManager.getSingleton() 
        self.SchemeManager = PyCEGUI.SchemeManager.getSingleton() 
        self.FontManager = PyCEGUI.FontManager.getSingleton() 

    def __del__(self): 
        PyCEGUIOpenGLRenderer.OpenGLRenderer.destroySystem() 

    def initializeResources(self, resourcePath): 
        """Initializes CEGUI's resource groups. This must be called before attempting to setup a UI. 

        """ 
        rp = self.System.getResourceProvider() 

        # Setup our resource directories 
        rp.setResourceGroupDirectory("schemes", resourcePath + "/schemes") 
        rp.setResourceGroupDirectory("imagesets", resourcePath + "/imagesets") 
        rp.setResourceGroupDirectory("fonts", resourcePath + "/fonts") 
        rp.setResourceGroupDirectory("layouts", resourcePath + "/layouts") 
        rp.setResourceGroupDirectory("looknfeels", resourcePath + "/looknfeel") 
        rp.setResourceGroupDirectory("schemas", resourcePath + "/xml_schemas") 
        
        # Set default resource groups 
        PyCEGUI.Imageset.setDefaultResourceGroup("imagesets") 
        PyCEGUI.Font.setDefaultResourceGroup("fonts") 
        PyCEGUI.Scheme.setDefaultResourceGroup("schemes") 
        PyCEGUI.WidgetLookManager.setDefaultResourceGroup("looknfeels") 
        PyCEGUI.WindowManager.setDefaultResourceGroup("layouts") 
        
        # Get our xml parser 
        parser = self.System.getXMLParser() 
        if parser.isPropertyPresent("SchemaDefaultResourceGroup"): 
            parser.setProperty("SchemaDefaultResourceGroup", "schemas")      

    def enableInputHandling(self): 
        # Mouse button handling 
        for button, cegui_name in self.buttons.iteritems(): 
            base.accept(button, self.captureButton, [button, cegui_name]) 

        # Turn off compound events so we can actually use the modifier keys 
        # as expected in a game. 
        base.mouseWatcherNode.setModifierButtons(ModifierButtons()) 
        base.buttonThrowers[0].node().setModifierButtons(ModifierButtons()) 

        # Key handling 
        for key, keyTuple in self.keys.iteritems(): 
            base.accept(key, self.captureKeys, [key, keyTuple]) 
            base.accept(key + '-up', self.captureKeys, [key + '-up', keyTuple]) 
            base.accept(key + '-repeat', self.captureKeys, [key, keyTuple]) 

        if (self.hideSystemCursor): 
            self.props.setCursorHidden(True) 
            base.win.requestProperties(self.props) 

    def disableInputHandling(self): 
        # Mouse button handling 
        for button, name in self.buttons.iteritems(): 
            base.ignore(button) 

        # Key handling 
        for key, keyTuple in self.keys.iteritems(): 
            base.ignore(key) 
            base.ignore(key + '-up') 
            base.ignore(key + '-repeat') 

        if (self.hideSystemCursor): 
            self.props.setCursorHidden(False) 
            base.win.requestProperties(self.props) 

    def enable(self): 
        self.enableInputHandling() 
        _renderingEnabled = True 

    def disable(self): 
        self.disableInputHandling() 
        _renderingEnabled = False 
    
    def captureKeys(self, key, keyTuple): 
        cegui_key = keyTuple[0] 
        key_ascii = keyTuple[1] 
        key_shift = keyTuple[2] 

        if key.find('shift') > 0: 
            if key.endswith('-up'): 
                if self._shiftCount > 0: 
                    self._shiftCount -= 1 
            else: 
                self._shiftCount += 1 
        
        elif key == 'caps_lock': 
            self._capsLock = not self._capsLock 

        elif key.endswith('-up'): 
            self.System.injectKeyUp(cegui_key) 
        
        else: 
            self.System.injectKeyDown(cegui_key) 
            if key_ascii != '': 
                if self._shiftCount > 0: 
                    if self._capsLock and key_ascii in string.lowercase: 
                        self.System.injectChar(ord(key_ascii)) 

                    else: 
                        self.System.injectChar(ord(key_shift)) 

                elif self._capsLock and key_ascii in string.lowercase: 
                    self.System.injectChar(ord(key_shift)) 

                else: 
                    self.System.injectChar(ord(key_ascii)) 

    def captureButton(self, button, name): 
        if button == 'wheel_up': 
            self.System.injectMouseWheelChange(1) 
        elif button == 'wheel_down': 
            self.System.injectMouseWheelChange(-1) 
        elif button.endswith('-up'): 
            self.System.injectMouseButtonUp(self.buttons[button]) 
        else: 
            self.System.injectMouseButtonDown(self.buttons[button]) 

    def windowEvent(self, window):
	    self.System.notifyDisplaySizeChanged(PyCEGUI.Size(window.getXSize(), window.getYSize()))

    def renderCallback(self, data): 
        if self._renderingEnabled: 
            dt = globalClock.getDt() 
            self.System.injectTimePulse(dt) 

            if base.mouseWatcherNode.hasMouse(): 
                x = base.win.getXSize() * (1 + base.mouseWatcherNode.getMouseX()) / 2 
                y = base.win.getYSize() * (1 - base.mouseWatcherNode.getMouseY()) / 2 
                self.System.injectMousePosition(x, y) 

            self.System.renderGUI()

test.py:

# -*- coding: utf-8 -*-
"""Panda/PyCEGUI Integration Module

Copyright (c) 2011 Christopher S. Case
Licensed under the MIT license; see the LICENSE file for details.

"""
from direct.showbase.ShowBase import ShowBase

# PandaCEGUI import
from pcegui import PandaCEGUI

class MyApp(ShowBase):
 
    def __init__(self):
        ShowBase.__init__(self)

        # Instantiate CEGUI helper class
        self.CEGUI = PandaCEGUI()

        # Setup CEGUI resources
        self.CEGUI.initializeResources('./datafiles')

        # Setup our CEGUI layout
        self.setupUI()

        # Enable CEGUI Rendering/Input Handling
        self.CEGUI.enable()

    def setupUI(self):
        self.CEGUI.SchemeManager.create("VanillaSkin.scheme")
        self.CEGUI.SchemeManager.create("TaharezLook.scheme")
        self.CEGUI.System.setDefaultMouseCursor("Vanilla-Images", "MouseArrow")

        root = self.CEGUI.WindowManager.loadWindowLayout("VanillaWindows.layout")
        self.CEGUI.System.setGUISheet(root)
        
        self.wnd = self.CEGUI.WindowManager.createWindow("TaharezLook/FrameWindow", "Demo Window")
        root.addChildWindow(self.wnd)

app = MyApp()
app.run()

Code’s (a little less) ugly, and it’s certainly not done yet, but I wanted to post it. As I hack on it today, I’ll update this post.

Setup:

  • Install PyCEGUI from pypi if on windows, or install/compile CEGUI 0.7.5 with python support for your respective platform. (I can help people to get it to compile on ubuntu based systems, took me about an hour and a half lastnight to figure out all the little gotchas)

  • Copy the CEGUI datafiles to the same directory as the script, in a ‘datafiles’ directory. (After a ‘make install’, mine were in ‘/usr/local/share/CEGUI’)

Changelog:
v0.0.9:

  • Added MIT license to the code.

v0.0.8:

  • Added code to notify CEGUI about window resize events.

v0.0.7:

  • Shift/Capslock handling fixed.
  • All known bugs fixed.
  • Beta status.

v0.0.6:

  • Key injection fixed.
  • Shift is correctly respected, however there are some caveats. (See Known bugs.)

v0.0.5:

  • Added initial key injection support. (keys are correctly injected, but shift isn’t being respected for character injection to input controls. Basically, all we get is lowecare characters)

v0.0.4:

  • Added Mouse button support. (left, right, middle, vertical scroll wheel)
  • Added system cursor hiding (and boolean to control behavior)
  • Now has feature parity with the PyCEGUI example on the cegui wiki.

v0.0.3:

  • Moved code into the PandaCEGUI module
  • Simplified test script
  • No functionality changes

v0.0.2:

  • Injecting mouse pointer
  • Injecting ticks

v0.0.1:

  • CEGUI renders correctly, no interaction. Tick injection missing.

Known bugs:

All known bugs are gone, but I still want to think about adding some nice integration between CEGUI’s event system and panda’s, if it makes sense. I’ll need to think about that…

Thanks man, i will surely try this after a weekend. i really wanted dgui alternative.

No problem. I’ve got a quarter of the work done for adding keyboard support; if I get time this weekend, I’ll try to get that out.

I’ve been debating making PandaCEGUI inherit from DirectObject… it was suggested to me to clean up some of the required keybinding code… but I’m not sure if this really needs that, or not.

Anyone have any thoughts?

You should go to IRC and have disscussion with rdb about that, he also mentioned something about wanting to integrate CEGUI in panda, and you may get some useful input from him and others on IRC.

Cheers :slight_smile:

Interesting to see a Python implementation. I’ve made a C++ implementation (based on the OpenGL renderer) myself, but ‘nik’ has made a more proper native C++ implementation at the forums here - although it is made for the old CEGUI version.

Regardless, great work - this will certainly be useful to the people who don’t want to compile C++ code in order to use CEGUI.

I would love to see panda with a ‘proper’ integration (something accessible in both C++ and python), but for the time being, a pure python implementation makes a lot of sense for me.

The one problem I’m having, however, is finding the best way to capture keyboard input. I’ve looked at how nik’s implementation does things, and I’m not sure how emulate that in python.

At the moment, I’m working on a solution that uses ‘base.accept’, which will work, but involves a rather lengthy dictionary of mappings…

A DataNode implementation (as nik uses) would be perfect, but at this very moment our wrapper-generator doesn’t support overriding virtual methods from Python, so that’s not possible for now.

I figured it was something like that. For now, this will work well enough. Seems pretty fast and although the keyboard handling isn’t pretty, it seems like it will work well enough.

i get following error when running test.py

self.CEGUI.SchemeManager.create("VanillaSkin.scheme") 
RuntimeError: CEGUI::InvalidRequestException in file ..\..\..\cegui\src\CEGUIDefaultResourceProvider.cpp(103) : DefaultResourceProvider::load: ./datafiles/schemes/VanillaSkin.scheme does not exist

Installed pycegui in

C:\Panda3D-1.7.1\python\

any ideas on how to fix this?

It looks like some of the data files are missing.
Did you remember to copy them to the same directory as test.py?

Oh thanks, didnt read post fully :slight_smile:

This is an awesome CEGUI binding!
Quick question: How do you bind a function to a widget, i.e, how do you call a function when a button is pressed?

Is possible to see a real use of Cegui into Panda?
I mean with event on button, use of properties like setPosition, and so on…

The window of RFI is very interesting for example…

Sounds amazing project.
But i have some problem …

RuntimeError: CEGUI::RendererException in file ..\..\..\..\cegui\src\RendererModules\OpenGL\CEGUIOpenGLWGLPBTextureTarget.cpp(210) : OpenGLWGLPBTextureTarget::initialisePBuffer - pbuffer creation failure, wglShareLists() call failed.

Someone knows how to fix it?

I´m using the last buildbot. When i reinstall Panda3D 1.7.2 all features working fine.
Thanx dude! I´m going crazy with DirectGUI.
Why don´t put this in panda3d runtime repo?

Thanx again

Hi,

I tried this with Panda3d 1.8.0 and Cegui 0.7.6, Arch Linux x86.

It worked… kind of… see the screenshot:

Label Name: is missing, text field is not shown from beginning and Namespace Viewer window is not split in 2 parts. This is not a problem with Cegui/PyCegui - a sample from pycegui web site works just fine and everything renders correctly.

Some other issues include:

  • CPU usage skyrockets (to 100%) when window is on not visible desktop (with other apps this does not happen)
  • closing the panda window does not end application, have to hit ctrl-c to stop it.
  • from time to time I get exception:
File "/pcegui.py", line 275, in windowEvent
    self.System.notifyDisplaySizeChanged(PyCEGUI.Size(window.getXSize(), window.getYSize()))
RuntimeError: CEGUI::RendererException in file CEGUIOpenGLTexture.cpp(205) : OpenGLTexture::setTextureSize: size too big

Any hints how to make it work? Or is there any better way to use Cegui in Panda3d?

Regards,
Garagoth.

Using Panda 1.8.0 and PyCEGUI 0.7.5, I have not yet seen the poor GUI rendering. However, I was seeing the issue where python would not exit after closing the Panda window.

This is due to PandaCEGUI catching window-event. I simply refactored things a little. Here is the relevant code:

    def __init__(self):
        # Panda Setup
        ceguiCB = PythonCallbackObject(self.renderCallback)
        self.cbNode = CallbackNode("CEGUI")
        self.cbNode.setDrawCallback(ceguiCB)
        render2d.attachNewNode(self.cbNode)
       
        # Initialize the OpenGLRenderer
        PyCEGUIOpenGLRenderer.OpenGLRenderer.bootstrapSystem()
        self.props = WindowProperties()
        # For convienence
        self.System = PyCEGUI.System.getSingleton()
        self.WindowManager = PyCEGUI.WindowManager.getSingleton()
        self.SchemeManager = PyCEGUI.SchemeManager.getSingleton()
        self.FontManager = PyCEGUI.FontManager.getSingleton()
            
    def enable(self):
        base.accept('window-event', self.windowEvent)
        self.enableInputHandling()
        _renderingEnabled = True
        
    def disable(self):
        base.accept('window-event', base.windowEvent)
        self.disableInputHandling()
        _renderingEnabled = False
            
    def windowEvent(self, window):
        self.System.notifyDisplaySizeChanged(PyCEGUI.Size(window.getXSize(), window.getYSize()))
        base.windowEvent(window)

[/code]

Wow! Very nice! Thanks for sharing

awesome snippet! merci!

Is there a way to include it into packp3d to share p3D or exe?