Using CEGUI with Panda in C++

This is a small Panda/CEGUI hello world for C++. You’ll need CEGUI 0.7.2 (which is the last version when I’m writing this) and the current CVS version of Panda (at the 2.0 branch) if you want it to work both in OpenGL and DirectX. The OpenGL support should work in Panda 1.7.

In the code we just load a model and create a CEGUI window. Since it’s meant to be a short example we only inject the mouse-down and mouse-up events for CEGUI, which are enough to move the CEGUI window around.

Here I’m linking to both the OpenGL and Direct3D9 CEGUI renderers, and I’ll initialize the correct one on runtime depending on what the user is running. As an obvious consequence of using CEGUI native renderers, this solution doesn’t support software rendering (tinydisplay). A custom Panda3D CEGUI renderer should be written if you need tinydisplay support.

#include "pandaFramework.h"
#include "pandaSystem.h"
 
#include "CEGUI.h"
#include "CEGUISystem.h"
#include "RendererModules/Direct3D9/CEGUIDirect3D9Renderer.h"
#include "RendererModules/OpenGL/CEGUIOpenGLRenderer.h"

#include "callbackData.h"
#include "callbackNode.h"
#include "callbackObject.h"

#include "mouseWatcher.h"

PandaFramework framework;
 

// Panda will call this every frame to render CEGUI
class GUIUpdateCallback : public CallbackObject { 
public: 
  ALLOC_DELETED_CHAIN(GUIUpdateCallback); 
  void do_callback(CallbackData *cbdata){
    CEGUI::System *guiSystem = CEGUI::System::getSingletonPtr();
    if (guiSystem){
      guiSystem->renderGUI();
    }
    if (cbdata){
      cbdata->upcall();
    }
  }
}; 
CEGUI::Direct3D9Renderer *cegui_dx_renderer=0;

// These callbacks are only used when D3D is running. They notice
// CEGUI that the window is being resized.
class GUIPreResetCallback : public CallbackObject { 
public: 
  ALLOC_DELETED_CHAIN(GUIPreResetCallback); 
  void do_callback(CallbackData *cbdata){
    if (cegui_dx_renderer){
      cegui_dx_renderer->preD3DReset();
    }
  }
}; 
class GUIPostResetCallback : public CallbackObject { 
public: 
  ALLOC_DELETED_CHAIN(GUIPostResetCallback); 
  void do_callback(CallbackData *cbdata){
    if (cegui_dx_renderer){
      cegui_dx_renderer->postD3DReset();
    }
  }
}; 

WindowFramework *window = 0;
PT(MouseWatcher) mouse_watcher = 0;

// main task to keep cegui updated about the mouse cursor position
AsyncTask::DoneStatus main_task (GenericAsyncTask* task, void* data) {
  if (mouse_watcher->has_mouse()){
    if (window){
      GraphicsWindow *gwin = window->get_graphics_window();
      if (gwin){
        int mouse_x = gwin->get_pointer(0).get_x();
        int mouse_y = gwin->get_pointer(0).get_y();
        CEGUI::System::getSingleton().injectMousePosition(mouse_x, mouse_y);
      }
    }
  }
  return AsyncTask::DS_cont;
}

// mouse events:
void mouse_down(const Event *ev, void *data){
  CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::LeftButton);
}
void mouse_up(const Event *ev, void *data){
  CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::LeftButton);
}

int main(int argc, char *argv[]) {

    // Load the window and set its title.
    framework.open_framework(argc, argv);
    framework.set_window_title("My Panda3D Window");

    // We want to hide the OS cursor since CEGUI has its own cursors.
    // (We could also hide CEGUI cursors instead...)
    WindowProperties &props = WindowProperties::get_default();
    props.set_cursor_hidden(true);
    window = framework.open_window(props,0);
 
    // Load some model to test the CEGUI window semitransparency
    NodePath smiley = window->load_model(framework.get_models(), "models/panda");
    smiley.reparent_to(window->get_render());
    window->get_camera_group().set_pos(30,-30,30);
    window->get_camera_group().look_at(0,0,0);
 
    void *d3d_device = window->get_graphics_output()->get_gsg()->get_d3d_device();

    // Here is where we initialize CEGUI's rendering
    if (d3d_device){ // We are running DirectX
      cegui_dx_renderer = & CEGUI::Direct3D9Renderer::bootstrapSystem((LPDIRECT3DDEVICE9)d3d_device);
      GraphicsStateGuardian *gsg = window->get_graphics_output()->get_gsg();
      gsg->set_draw_callback(new GUIUpdateCallback());
      gsg->set_pre_reset_callback(new GUIPreResetCallback());
      gsg->set_post_reset_callback(new GUIPostResetCallback());
    } else { // We are running OpenGL. (We assume tiny is disabled.)
      CEGUI::OpenGLRenderer::bootstrapSystem();
      PT(CallbackNode) callback_node = new CallbackNode("CEGUI"); 
      callback_node->set_draw_callback(new GUIUpdateCallback()); 
      window->get_render_2d().attach_new_node(callback_node);
    }

    // Let's add a task that we'll use to keep CEGUI updated about the mouse cursor
    // position.
    PT(GenericAsyncTask) main_task_object = new GenericAsyncTask("main task", &main_task, (void*) NULL);
    AsyncTaskManager::get_global_ptr()->add(main_task_object);

    mouse_watcher = (MouseWatcher*)window->get_mouse().node();

    // For this sample code we are just gonna setup two input events, mouse button left up and
    // down, it's enough to drag the window around.
    window->enable_keyboard();
    framework.define_key ( "mouse1", "mouse1", &mouse_down, (void*)0);
    framework.define_key ( "mouse1-up", "mouse1-up", &mouse_up, (void*)0);

    // This is CEGUI code to create a window taken from the CEGUI tutorials.
    // From here on you can just refer to the CEGUI documentation.
    using namespace CEGUI;
    SchemeManager::getSingleton().create("TaharezLook.scheme");
    System::getSingleton().setDefaultMouseCursor("TaharezLook", "MouseArrow");
    
    WindowManager& wmgr = WindowManager::getSingleton();

    Window* myRoot = wmgr.createWindow( "DefaultWindow", "root" );
    System::getSingleton().setGUISheet( myRoot );

    FrameWindow* fWnd = static_cast<FrameWindow*>(
    wmgr.createWindow( "TaharezLook/FrameWindow", "testWindow" ));

    myRoot->addChildWindow( fWnd );

    fWnd->setPosition( UVector2( UDim( 0.25f, 0 ), UDim( 0.25f, 0 ) ) );
    fWnd->setSize( UVector2( UDim( 0.5f, 0 ), UDim( 0.5f, 0 ) ) );
    fWnd->setVisible(true);
    fWnd->setAlpha(0.5f);
    fWnd->setAlwaysOnTop(true);
    fWnd->setText( "Hello World!" );

    // Run the engine.
    framework.main_loop();
    // Shut down the engine when done.
    framework.close_framework();
    return (0);
}

Will there be python support for it anytime soon?

Uhm, this version only does mouse presses. Nik’s data node supports much more events, I advise grabbing that from his implementation.

The was some work on it by an user named Nik but it was for an old version of Cegui (0.6). CEGUI 0.7 was redesigned and it’s much faster (CEGUI 0.6 and previous were painfully slow). Look in features in development.

As I tried to explain above, the focus of the example is rendering (to show how to use the callbacks I committed yesterday) and I intentionally kept it short, and there’re no key presses because I didn’t create a textbox to write on in this example. I don’t see why a C++ coder would have difficulty surmising how to inject the rest of the events. The input system of cegui is brain dead simple and in fact you are not even supposed to plug “them” as part of the implementation of cegui otherwise the user will have problems disabling and enabling cegui input to change from game mode to gui mode. I dislike a black box integration in this case, this is no python, no reason to lose flexibility by encapsulating everything. See Ogre3D as an example, CEGUI is already integrated but they leave the input for the user to setup. So I would suggest any c++ user to just add the CEGUI events he needs from the list below just like in my example. (This is all in CEGUI documentation).