Difference between revisions of "Using CEGUI with Producer and OpenGL"

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Jump to: navigation, search
m (Using it all together)
(Fixed code formatting)
Line 4: Line 4:
 
The first step is to create some sort of "viewer" with Producer, basically make a Producer::RenderSurface and a Producer::KeyboardMouse.  Both of these classes are necessary for using CEGUI correctly.  So, here is some example viewer code:
 
The first step is to create some sort of "viewer" with Producer, basically make a Producer::RenderSurface and a Producer::KeyboardMouse.  Both of these classes are necessary for using CEGUI correctly.  So, here is some example viewer code:
  
#include "Viewer.h"
+
#include "Viewer.h"
Viewer::Viewer() : _camera(new Producer::Camera()), _rs(0), _kbm(0)
+
Viewer::Viewer() : _camera(new Producer::Camera()), _rs(0), _kbm(0)
{
+
{
  _rs = _camera->getRenderSurface();
+
  _rs = _camera->getRenderSurface();
  _kbm = new Producer::KeyboardMouse(_rs.get());
+
  _kbm = new Producer::KeyboardMouse(_rs.get());
}
+
}
 
+
Viewer::~Viewer() {}
+
Viewer::~Viewer() {}
  
  
Line 18: Line 18:
  
  
Application::Application(const std::string& fontfile):
+
Application::Application(const std::string& fontfile):
  _status(RUNNING), _viewer(new general::Viewer()), _sim(new example::TeapotSim()),
+
  _status(RUNNING), _viewer(new general::Viewer()), _sim(new example::TeapotSim()),
  _scheme_loader_policy("MouseArrow"), _layout_loader_policy(), _script_module(new ApplicationEventHandler())
+
  _scheme_loader_policy("MouseArrow"), _layout_loader_policy(), _script_module(new ApplicationEventHandler())
{
+
{
  // OpenGL context specific initializations
+
  // OpenGL context specific initializations
  Producer::ref_ptr<Producer::RenderSurface> rs = _viewer->GetRenderSurface();
+
  Producer::ref_ptr<Producer::RenderSurface> rs = _viewer->GetRenderSurface();
  rs->addRealizeCallback(new general::InitGL());
+
  rs->addRealizeCallback(new general::InitGL());
  rs->addRealizeCallback(new prce::InitCEGUI(fontfile,_script_module));
+
  rs->addRealizeCallback(new prce::InitCEGUI(fontfile,_script_module));
  rs->setWindowName("Producer and CEGUI example");
+
  rs->setWindowName("Producer and CEGUI example");
  rs->setWindowRectangle(20,50,800,600);
+
  rs->setWindowRectangle(20,50,800,600);
 
+
  // rendering stuff
+
  // rendering stuff
  _viewer->GetCamera()->setSceneHandler( _sim.get() );
+
  _viewer->GetCamera()->setSceneHandler( _sim.get() );
  _viewer->GetCamera()->addPreDrawCallback( new SimulationDrawContext(_sim.get()) );
+
  _viewer->GetCamera()->addPreDrawCallback( new SimulationDrawContext(_sim.get()) );
  _viewer->GetCamera()->addPostDrawCallback(new prce::RenderCEGUI() ); // renders the GUI over scene
+
  _viewer->GetCamera()->addPostDrawCallback(new prce::RenderCEGUI() ); // renders the GUI over scene
 
+
  // device input stuff
+
  // device input stuff
  Producer::ref_ptr<Producer::KeyboardMouse> kbm = _viewer->GetKeyboardMouse();
+
  Producer::ref_ptr<Producer::KeyboardMouse> kbm = _viewer->GetKeyboardMouse();
  prce::Injector* injector = new prce::Injector( kbm.get() );
+
  prce::Injector* injector = new prce::Injector( kbm.get() );
  kbm->setCallback( injector );
+
  kbm->setCallback( injector );
  kbm->startThread();
+
  kbm->startThread();
}
+
}
  
 
The general::InitGL functor and prce::InitCEGUI functor are quite important.  These initializations are OpenGL specific, and must occur within a thread with the right OpenGL context.  The Producer::RenderSurface can execute one-time initializations if you add them to be called upon window realization.  Actually, a lot is happening here in the Application's constructor.  You might be able to imagine how OpenGL is initialized, so I'll skip that for now.  The Camera's draw callbacks are very important, but first I want to show an example of initializing CEGUI:
 
The general::InitGL functor and prce::InitCEGUI functor are quite important.  These initializations are OpenGL specific, and must occur within a thread with the right OpenGL context.  The Producer::RenderSurface can execute one-time initializations if you add them to be called upon window realization.  Actually, a lot is happening here in the Application's constructor.  You might be able to imagine how OpenGL is initialized, so I'll skip that for now.  The Camera's draw callbacks are very important, but first I want to show an example of initializing CEGUI:
  
#include <CEGUISystem.h>
+
#include <CEGUISystem.h>
#include <renderers/OpenGLGUIRenderer/openglrenderer.h>
+
#include <renderers/OpenGLGUIRenderer/openglrenderer.h>
#include <CEGUIScriptModule.h>
+
#include <CEGUIScriptModule.h>
#include <CEGUIFontManager.h>
+
#include <CEGUIFontManager.h>
 
+
#include "InitCEGUI.h"
+
#include "InitCEGUI.h"
 
+
using namespace prce;
+
using namespace prce;
 
+
InitCEGUI::InitCEGUI(const std::string& font, CEGUI::ScriptModule* s): _sm(s), _font(font)
+
InitCEGUI::InitCEGUI(const std::string& font, CEGUI::ScriptModule* s): _sm(s), _font(font)
{
+
{
}
+
}
 
+
InitCEGUI::~InitCEGUI()
+
InitCEGUI::~InitCEGUI()
{
+
{
}
+
}
 
+
void InitCEGUI::operator ()(const Producer::RenderSurface& rs)
+
void InitCEGUI::operator ()(const Producer::RenderSurface& rs)
{
+
{
  if( _sm )
+
  if( _sm )
    new CEGUI::System( new CEGUI::OpenGLRenderer(0), _sm );
+
    new CEGUI::System( new CEGUI::OpenGLRenderer(0), _sm );
  else
+
  else
    new CEGUI::System( new CEGUI::OpenGLRenderer(0) );
+
    new CEGUI::System( new CEGUI::OpenGLRenderer(0) );
 
+
  CEGUI::FontManager* fm = CEGUI::FontManager::getSingletonPtr();
+
  CEGUI::FontManager* fm = CEGUI::FontManager::getSingletonPtr();
  CEGUI::Font* f = fm->createFont( _font );
+
  CEGUI::Font* f = fm->createFont( _font );
}
+
}
  
 
Producer uses OpenGL for rendering, so that is the renderer we must choose.  Luckily, CEGUI provides this.  You will need to provide a CEGUI::ScriptModule when initializing the System, but that is for another lesson.  A default font needs to be provided to the System, and during initialization is a good time to provide that, so my functor has required it.
 
Producer uses OpenGL for rendering, so that is the renderer we must choose.  Luckily, CEGUI provides this.  You will need to provide a CEGUI::ScriptModule when initializing the System, but that is for another lesson.  A default font needs to be provided to the System, and during initialization is a good time to provide that, so my functor has required it.
Line 76: Line 76:
 
The application code showed a post draw callback being set.  Producer will execute this little callback at the right time, within the correct OpenGL context for OpenGL calls.  Here is what needs to happen for CEGUI to appear on your screen each frame:
 
The application code showed a post draw callback being set.  Producer will execute this little callback at the right time, within the correct OpenGL context for OpenGL calls.  Here is what needs to happen for CEGUI to appear on your screen each frame:
  
void RenderCEGUI::operator ()(const Producer::Camera& c)
+
void RenderCEGUI::operator ()(const Producer::Camera& c)
{
+
{
  CEGUI::System::getSingleton().renderGUI();
+
  CEGUI::System::getSingleton().renderGUI();
}
+
}
  
 
=== Injecting Input to CEGUI via Producer ===
 
=== Injecting Input to CEGUI via Producer ===
 
This is where the real magic happens.  You must inject inputs into CEGUI because it does not poll the operating system for you.  That is the job of a tool like Producer.  For the most part, the following code works.  There is a small bug that is only seen when resizing the Producer::RenderSurface (window).  However, I think it provides insight into how injections need to be done for CEGUI to react to user input:
 
This is where the real magic happens.  You must inject inputs into CEGUI because it does not poll the operating system for you.  That is the job of a tool like Producer.  For the most part, the following code works.  There is a small bug that is only seen when resizing the Producer::RenderSurface (window).  However, I think it provides insight into how injections need to be done for CEGUI to react to user input:
  
/** \author John K. Grant
+
/** \author John K. Grant
  * \date July, 14 2005.
+
  * \date July, 14 2005.
  * This code is public domain, free for use, with the author having no responsibility or liability.
+
  * This code is public domain, free for use, with the author having no responsibility or liability.
  */
+
  */
#include <CEGUISystem.h>
+
#include <CEGUISystem.h>
#include "SpecialKeyMapFunctor.h"
+
#include "SpecialKeyMapFunctor.h"
#include "Injector.h"
+
#include "Injector.h"
 +
 +
using namespace prce;
 +
 +
Injector::Injector(Producer::KeyboardMouse* kbm): _kbm(kbm)
 +
{
 +
  prce::SpecialKeyMapFunctor specials;
 +
  _keys = specials();
 +
}
 +
 +
Injector::~Injector()
 +
{
 +
}
 +
 +
/** changes from producer coordinates to normalized CE coordinates */
 +
void Injector::_producer_to_cegui(float& mx, float& my)
 +
{
 +
    // --- horizontal math
 +
    // ph = 1.0 - -1.0 = 2.0    // total horizontal distance in producer
 +
    // ch = 1.0 -  0.0 = 1.0    // total horizontal distance in ce
 +
 +
    // --- vertical math
 +
    // pv = 1.0 - -1.0 =  2.0  // total vertical distance in producer
 +
    // cv = 0.0 -  1.0 = -1.0  // total vertical distance in ce
 +
 +
    // cex = cx + px * (ch/2 / ph/2) = 0.5 + px * ( 0.5/1.0) = 0.5 + px*0.5
 +
    // cey = cy + py * (cv/2 / pv/2) = 0.5 + py * (-0.5/1.0) = 0.5 - py*0.5
 +
    // where cx is the "center" x value in ce
 +
    // and  cy is the "center" y value in ce
 +
    float cex(0.5 + mx*0.5);
 +
    float cey(0.5 - my*0.5);
 +
    mx = cex;
 +
    my = cey;
 +
}
 +
 +
/** takes normalized CEGUI coordinates and converts
 +
  * into pixel values for the screen.
 +
  */
 +
void Injector::_convert_to_screen(float& mx, float& my)
 +
{
 +
    unsigned int width(  _kbm->getRenderSurface()->getWindowWidth()  );
 +
    unsigned int height( _kbm->getRenderSurface()->getWindowHeight() );
 +
 +
    // the above yields the same results as the code below
 +
    //int wx(0), wy(0);
 +
    //unsigned int width(0), height(0);
 +
    //_kbm->getRenderSurface()->getWindowRectangle(wx,wy,width,height);
 +
 +
    float pixel_x = (float)width  * mx;
 +
    float pixel_y = (float)height * my;
 +
    mx = pixel_x;
 +
    my = pixel_y;
 +
}
 +
 +
void Injector::mouseMotion(float px,float py)
 +
{
 +
    _producer_to_cegui(px,py);
 +
    _convert_to_screen(px,py);
 +
    CEGUI::System::getSingleton().injectMousePosition(px, py);
 +
}
 +
 +
void Injector::passiveMouseMotion(float px,float py)
 +
{
 +
    _producer_to_cegui(px,py);
 +
    _convert_to_screen(px,py);
 +
    CEGUI::System::getSingleton().injectMousePosition(px, py);
 +
}
 +
 +
void Injector::buttonPress(float px,float py,unsigned int button)
 +
{
 +
    _producer_to_cegui(px,py);
 +
    _convert_to_screen(px,py);
 +
    CEGUI::System::getSingleton().injectMousePosition(px, py);
 +
 +
    if( button == 1 )  // left
 +
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::LeftButton);
 +
 +
    else if( button == 2 )  // middle
 +
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::MiddleButton);
 +
 +
    else if( button == 3 )  // right
 +
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::RightButton);
 +
}
 +
 +
void Injector::buttonRelease(float px,float py,unsigned int button)
 +
{
 +
    _producer_to_cegui(px,py);
 +
    _convert_to_screen(px,py);
 +
    CEGUI::System::getSingleton().injectMousePosition(px, py);
 +
 +
    if( button == 1 )  // left
 +
      CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::LeftButton);
 +
 +
    else if( button == 2 )  // middle
 +
      CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::MiddleButton);
 +
 +
    else if( button == 3 )  // right
 +
      CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::RightButton);
 +
}
 +
 +
void Injector::doubleButtonPress(float px,float py,unsigned int button)
 +
{
 +
    /** currently is implemented to only inject a single press.
 +
      * CEGUI handles its own detection of the double click via
 +
      * the "time pulse", which controls other things as well.
 +
      */
 +
    _producer_to_cegui(px,py);
 +
    _convert_to_screen(px,py);
 +
    CEGUI::System::getSingleton().injectMousePosition(px,py);
 +
    if( button == 1 )  // left
 +
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::LeftButton);
 +
 +
    else if( button == 2 )  // middle
 +
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::MiddleButton);
 +
 +
    else if( button == 3 )  // right
 +
      CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::RightButton);
 +
}
 +
 +
void Injector::mouseScroll(enum Producer::KeyboardMouseCallback::ScrollingMotion motion)
 +
{
 +
    switch( motion )
 +
    {
 +
    case Producer::KeyboardMouseCallback::ScrollDown:
 +
      {
 +
          CEGUI::System::getSingleton().injectMouseWheelChange( -1 );
 +
      } break;
 +
 +
    case Producer::KeyboardMouseCallback::ScrollUp:
 +
      {
 +
          CEGUI::System::getSingleton().injectMouseWheelChange( 1 );
 +
      } break;
 +
 +
    default: // Producer::KeyboardMouseCallback::ScrollNone
 +
      break;
 +
    };
 +
}
 +
 +
void Injector::keyPress(Producer::KeyCharacter key)
 +
{
 +
    CEGUI::System::getSingleton().injectKeyDown( static_cast<CEGUI::uint>(key) );
 +
    CEGUI::System::getSingleton().injectChar( static_cast<CEGUI::utf32>( key ) );
 +
}
 +
 +
void Injector::keyRelease(Producer::KeyCharacter key)
 +
{
 +
    CEGUI::System::getSingleton().injectKeyUp( static_cast<CEGUI::uint>(key) );
 +
}
 +
 +
void Injector::specialKeyPress(Producer::KeyCharacter prkey)
 +
{
 +
    KeyMap::iterator iter = _keys.find( prkey );
 +
    if( iter != _keys.end() )
 +
    {
 +
      CEGUI::Key::Scan cekey = (*iter).second;
 +
      CEGUI::System::getSingleton().injectKeyDown( static_cast<CEGUI::uint>(cekey) );
 +
    }
 +
    CEGUI::System::getSingleton().injectChar( static_cast<CEGUI::utf32>( prkey ) );
 +
}
 +
 +
void Injector::specialKeyRelease(Producer::KeyCharacter prkey)
 +
{
 +
    KeyMap::iterator iter = _keys.find( prkey );
 +
    if( iter != _keys.end() )
 +
    {
 +
      CEGUI::Key::Scan cekey = (*iter).second;
 +
      CEGUI::System::getSingleton().injectKeyUp( static_cast<CEGUI::uint>(cekey) );
 +
    }
 +
}
  
using namespace prce;
+
=== Using it all together ===
 
+
Here is what a simple main() will look like:
Injector::Injector(Producer::KeyboardMouse* kbm): _kbm(kbm)
+
{
+
  prce::SpecialKeyMapFunctor specials;
+
  _keys = specials();
+
}
+
 
+
Injector::~Injector()
+
{
+
}
+
 
+
/** changes from producer coordinates to normalized CE coordinates */
+
void Injector::_producer_to_cegui(float& mx, float& my)
+
{
+
  // --- horizontal math
+
  // ph = 1.0 - -1.0 = 2.0    // total horizontal distance in producer
+
  // ch = 1.0 -  0.0 = 1.0    // total horizontal distance in ce
+
 
+
  // --- vertical math
+
  // pv = 1.0 - -1.0 =  2.0  // total vertical distance in producer
+
  // cv = 0.0 -  1.0 = -1.0  // total vertical distance in ce
+
 
+
  // cex = cx + px * (ch/2 / ph/2) = 0.5 + px * ( 0.5/1.0) = 0.5 + px*0.5
+
  // cey = cy + py * (cv/2 / pv/2) = 0.5 + py * (-0.5/1.0) = 0.5 - py*0.5
+
  // where cx is the "center" x value in ce
+
  // and  cy is the "center" y value in ce
+
  float cex(0.5 + mx*0.5);
+
  float cey(0.5 - my*0.5);
+
  mx = cex;
+
  my = cey;
+
}
+
 
+
/** takes normalized CEGUI coordinates and converts
+
  * into pixel values for the screen.
+
  */
+
void Injector::_convert_to_screen(float& mx, float& my)
+
{
+
  unsigned int width(  _kbm->getRenderSurface()->getWindowWidth()  );
+
  unsigned int height( _kbm->getRenderSurface()->getWindowHeight() );
+
 
+
  // the above yields the same results as the code below
+
  //int wx(0), wy(0);
+
  //unsigned int width(0), height(0);
+
  //_kbm->getRenderSurface()->getWindowRectangle(wx,wy,width,height);
+
 
+
  float pixel_x = (float)width  * mx;
+
  float pixel_y = (float)height * my;
+
  mx = pixel_x;
+
  my = pixel_y;
+
}
+
 
+
void Injector::mouseMotion(float px,float py)
+
{
+
  _producer_to_cegui(px,py);
+
  _convert_to_screen(px,py);
+
  CEGUI::System::getSingleton().injectMousePosition(px, py);
+
}
+
 
+
void Injector::passiveMouseMotion(float px,float py)
+
{
+
  _producer_to_cegui(px,py);
+
  _convert_to_screen(px,py);
+
  CEGUI::System::getSingleton().injectMousePosition(px, py);
+
}
+
 
+
void Injector::buttonPress(float px,float py,unsigned int button)
+
{
+
  _producer_to_cegui(px,py);
+
  _convert_to_screen(px,py);
+
  CEGUI::System::getSingleton().injectMousePosition(px, py);
+
 
+
  if( button == 1 )  // left
+
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::LeftButton);
+
 
+
  else if( button == 2 )  // middle
+
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::MiddleButton);
+
 
+
  else if( button == 3 )  // right
+
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::RightButton);
+
}
+
 
+
void Injector::buttonRelease(float px,float py,unsigned int button)
+
{
+
  _producer_to_cegui(px,py);
+
  _convert_to_screen(px,py);
+
  CEGUI::System::getSingleton().injectMousePosition(px, py);
+
 
+
  if( button == 1 )  // left
+
      CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::LeftButton);
+
 
+
  else if( button == 2 )  // middle
+
      CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::MiddleButton);
+
 
+
  else if( button == 3 )  // right
+
      CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::RightButton);
+
}
+
 
+
void Injector::doubleButtonPress(float px,float py,unsigned int button)
+
{
+
  /** currently is implemented to only inject a single press.
+
    * CEGUI handles its own detection of the double click via
+
    * the "time pulse", which controls other things as well.
+
    */
+
  _producer_to_cegui(px,py);
+
  _convert_to_screen(px,py);
+
  CEGUI::System::getSingleton().injectMousePosition(px,py);
+
  if( button == 1 )  // left
+
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::LeftButton);
+
 
+
  else if( button == 2 )  // middle
+
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::MiddleButton);
+
 
+
  else if( button == 3 )  // right
+
      CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::RightButton);
+
}
+
  
void Injector::mouseScroll(enum Producer::KeyboardMouseCallback::ScrollingMotion motion)
+
int main(unsigned int argc, char* argv[])
{
+
{
   switch( motion )
+
   if( argc < 4 )
 
   {
 
   {
  case Producer::KeyboardMouseCallback::ScrollDown:
+
    PrintUsage();
      {
+
    return 1;
        CEGUI::System::getSingleton().injectMouseWheelChange( -1 );
+
      } break;
+
 
+
  case Producer::KeyboardMouseCallback::ScrollUp:
+
      {
+
        CEGUI::System::getSingleton().injectMouseWheelChange( 1 );
+
      } break;
+
 
+
  default: // Producer::KeyboardMouseCallback::ScrollNone
+
      break;
+
  };
+
}
+
 
+
void Injector::keyPress(Producer::KeyCharacter key)
+
{
+
  CEGUI::System::getSingleton().injectKeyDown( static_cast<CEGUI::uint>(key) );
+
  CEGUI::System::getSingleton().injectChar( static_cast<CEGUI::utf32>( key ) );
+
}
+
 
+
void Injector::keyRelease(Producer::KeyCharacter key)
+
{
+
  CEGUI::System::getSingleton().injectKeyUp( static_cast<CEGUI::uint>(key) );
+
}
+
 
+
void Injector::specialKeyPress(Producer::KeyCharacter prkey)
+
{
+
  KeyMap::iterator iter = _keys.find( prkey );
+
  if( iter != _keys.end() )
+
  {
+
      CEGUI::Key::Scan cekey = (*iter).second;
+
      CEGUI::System::getSingleton().injectKeyDown( static_cast<CEGUI::uint>(cekey) );
+
 
   }
 
   }
   CEGUI::System::getSingleton().injectChar( static_cast<CEGUI::utf32>( prkey ) );
+
}
+
   Producer::ref_ptr<example::Application> app = new example::Application(argv[1]);
 
+
  app->GetViewer()->GetRenderSurface()->realize();
void Injector::specialKeyRelease(Producer::KeyCharacter prkey)
+
{
+
  app->LoadCEGUIScheme( argv[2] );
   KeyMap::iterator iter = _keys.find( prkey );
+
   app->LoadCEGUILayout( argv[3] );
   if( iter != _keys.end() )
+
 +
   while( !app->IsFinished() )
 
   {
 
   {
      CEGUI::Key::Scan cekey = (*iter).second;
+
    app->GetViewer()->GetCamera()->frame();
      CEGUI::System::getSingleton().injectKeyUp( static_cast<CEGUI::uint>(cekey) );
+
 
   }
 
   }
}
+
 
+
  return 0;
=== Using it all together ===
+
}
Here is what a simple main() will look like:
+
 
+
int main(unsigned int argc, char* argv[])
+
{
+
  if( argc < 4 )
+
  {
+
    PrintUsage();
+
    return 1;
+
  }
+
 
+
  Producer::ref_ptr<example::Application> app = new example::Application(argv[1]);
+
  app->GetViewer()->GetRenderSurface()->realize();
+
 
+
  app->LoadCEGUIScheme( argv[2] );
+
  app->LoadCEGUILayout( argv[3] );
+
 
+
  while( !app->IsFinished() )
+
  {
+
    app->GetViewer()->GetCamera()->frame();
+
  }
+
 
+
  return 0;
+
}
+

Revision as of 10:00, 3 September 2005

Producer is a cross platform toolkit, best explained on its website. I use it for many things, but creating a window, or Render Surface, is the most fundamental task. CEGUI requires knowledge of the window, mouse, and keyboard interaction. I will show how you can do that with some example code.

Defining a Producer Viewer

The first step is to create some sort of "viewer" with Producer, basically make a Producer::RenderSurface and a Producer::KeyboardMouse. Both of these classes are necessary for using CEGUI correctly. So, here is some example viewer code:

#include "Viewer.h"
Viewer::Viewer() : _camera(new Producer::Camera()), _rs(0), _kbm(0)
{
  _rs = _camera->getRenderSurface();
  _kbm = new Producer::KeyboardMouse(_rs.get());
}

Viewer::~Viewer() {}


Using the viewer in an Application

And an application might use this viewer, doing some important initializations:


Application::Application(const std::string& fontfile):
  _status(RUNNING), _viewer(new general::Viewer()), _sim(new example::TeapotSim()),
  _scheme_loader_policy("MouseArrow"), _layout_loader_policy(), _script_module(new ApplicationEventHandler())
{
  // OpenGL context specific initializations
  Producer::ref_ptr<Producer::RenderSurface> rs = _viewer->GetRenderSurface();
  rs->addRealizeCallback(new general::InitGL());
  rs->addRealizeCallback(new prce::InitCEGUI(fontfile,_script_module));
  rs->setWindowName("Producer and CEGUI example");
  rs->setWindowRectangle(20,50,800,600);

  // rendering stuff
  _viewer->GetCamera()->setSceneHandler( _sim.get() );
  _viewer->GetCamera()->addPreDrawCallback( new SimulationDrawContext(_sim.get()) );
  _viewer->GetCamera()->addPostDrawCallback(new prce::RenderCEGUI() ); // renders the GUI over scene

  // device input stuff
  Producer::ref_ptr<Producer::KeyboardMouse> kbm = _viewer->GetKeyboardMouse();
  prce::Injector* injector = new prce::Injector( kbm.get() );
  kbm->setCallback( injector );
  kbm->startThread();
}

The general::InitGL functor and prce::InitCEGUI functor are quite important. These initializations are OpenGL specific, and must occur within a thread with the right OpenGL context. The Producer::RenderSurface can execute one-time initializations if you add them to be called upon window realization. Actually, a lot is happening here in the Application's constructor. You might be able to imagine how OpenGL is initialized, so I'll skip that for now. The Camera's draw callbacks are very important, but first I want to show an example of initializing CEGUI:

#include <CEGUISystem.h>
#include <renderers/OpenGLGUIRenderer/openglrenderer.h>
#include <CEGUIScriptModule.h>
#include <CEGUIFontManager.h>

#include "InitCEGUI.h"

using namespace prce;

InitCEGUI::InitCEGUI(const std::string& font, CEGUI::ScriptModule* s): _sm(s), _font(font)
{
}

InitCEGUI::~InitCEGUI()
{
}

void InitCEGUI::operator ()(const Producer::RenderSurface& rs)
{
  if( _sm )
    new CEGUI::System( new CEGUI::OpenGLRenderer(0), _sm );
  else
    new CEGUI::System( new CEGUI::OpenGLRenderer(0) );

  CEGUI::FontManager* fm = CEGUI::FontManager::getSingletonPtr();
  CEGUI::Font* f = fm->createFont( _font );
}

Producer uses OpenGL for rendering, so that is the renderer we must choose. Luckily, CEGUI provides this. You will need to provide a CEGUI::ScriptModule when initializing the System, but that is for another lesson. A default font needs to be provided to the System, and during initialization is a good time to provide that, so my functor has required it.

Rendering CEGUI with Producer

The application code showed a post draw callback being set. Producer will execute this little callback at the right time, within the correct OpenGL context for OpenGL calls. Here is what needs to happen for CEGUI to appear on your screen each frame:

void RenderCEGUI::operator ()(const Producer::Camera& c)
{
  CEGUI::System::getSingleton().renderGUI();
}

Injecting Input to CEGUI via Producer

This is where the real magic happens. You must inject inputs into CEGUI because it does not poll the operating system for you. That is the job of a tool like Producer. For the most part, the following code works. There is a small bug that is only seen when resizing the Producer::RenderSurface (window). However, I think it provides insight into how injections need to be done for CEGUI to react to user input:

/** \author John K. Grant
  * \date July, 14 2005.
  * This code is public domain, free for use, with the author having no responsibility or liability.
  */
#include <CEGUISystem.h>
#include "SpecialKeyMapFunctor.h"
#include "Injector.h"

using namespace prce;

Injector::Injector(Producer::KeyboardMouse* kbm): _kbm(kbm)
{
  prce::SpecialKeyMapFunctor specials;
  _keys = specials();
}

Injector::~Injector()
{
}

/** changes from producer coordinates to normalized CE coordinates */
void Injector::_producer_to_cegui(float& mx, float& my)
{
   // --- horizontal math
   // ph = 1.0 - -1.0 = 2.0    // total horizontal distance in producer
   // ch = 1.0 -  0.0 = 1.0    // total horizontal distance in ce

   // --- vertical math
   // pv = 1.0 - -1.0 =  2.0   // total vertical distance in producer
   // cv = 0.0 -  1.0 = -1.0   // total vertical distance in ce

   // cex = cx + px * (ch/2 / ph/2) = 0.5 + px * ( 0.5/1.0) = 0.5 + px*0.5
   // cey = cy + py * (cv/2 / pv/2) = 0.5 + py * (-0.5/1.0) = 0.5 - py*0.5
   // where cx is the "center" x value in ce
   // and   cy is the "center" y value in ce
   float cex(0.5 + mx*0.5);
   float cey(0.5 - my*0.5);
   mx = cex;
   my = cey;
}

/** takes normalized CEGUI coordinates and converts
  * into pixel values for the screen.
  */
void Injector::_convert_to_screen(float& mx, float& my)
{
   unsigned int width(  _kbm->getRenderSurface()->getWindowWidth()  );
   unsigned int height( _kbm->getRenderSurface()->getWindowHeight() );

   // the above yields the same results as the code below
   //int wx(0), wy(0);
   //unsigned int width(0), height(0);
   //_kbm->getRenderSurface()->getWindowRectangle(wx,wy,width,height);

   float pixel_x = (float)width  * mx;
   float pixel_y = (float)height * my;
   mx = pixel_x;
   my = pixel_y;
}

void Injector::mouseMotion(float px,float py)
{
   _producer_to_cegui(px,py);
   _convert_to_screen(px,py);
   CEGUI::System::getSingleton().injectMousePosition(px, py);
}

void Injector::passiveMouseMotion(float px,float py)
{
   _producer_to_cegui(px,py);
   _convert_to_screen(px,py);
   CEGUI::System::getSingleton().injectMousePosition(px, py);
}

void Injector::buttonPress(float px,float py,unsigned int button)
{
   _producer_to_cegui(px,py);
   _convert_to_screen(px,py);
   CEGUI::System::getSingleton().injectMousePosition(px, py);

   if( button == 1 )  // left
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::LeftButton);

   else if( button == 2 )  // middle
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::MiddleButton);

   else if( button == 3 )  // right
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::RightButton);
}

void Injector::buttonRelease(float px,float py,unsigned int button)
{
   _producer_to_cegui(px,py);
   _convert_to_screen(px,py);
   CEGUI::System::getSingleton().injectMousePosition(px, py);

   if( button == 1 )  // left
      CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::LeftButton);

   else if( button == 2 )  // middle
      CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::MiddleButton);

   else if( button == 3 )   // right
      CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::RightButton);
}

void Injector::doubleButtonPress(float px,float py,unsigned int button)
{
   /** currently is implemented to only inject a single press.
     * CEGUI handles its own detection of the double click via
     * the "time pulse", which controls other things as well.
     */
   _producer_to_cegui(px,py);
   _convert_to_screen(px,py);
   CEGUI::System::getSingleton().injectMousePosition(px,py);
   if( button == 1 )  // left
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::LeftButton);

   else if( button == 2 )  // middle
      CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::MiddleButton);

   else if( button == 3 )   // right
      CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::RightButton);
}

void Injector::mouseScroll(enum Producer::KeyboardMouseCallback::ScrollingMotion motion)
{
   switch( motion )
   {
   case Producer::KeyboardMouseCallback::ScrollDown:
      {
         CEGUI::System::getSingleton().injectMouseWheelChange( -1 );
      } break;

   case Producer::KeyboardMouseCallback::ScrollUp:
      {
         CEGUI::System::getSingleton().injectMouseWheelChange( 1 );
      } break;

   default: // Producer::KeyboardMouseCallback::ScrollNone
      break;
   };
}

void Injector::keyPress(Producer::KeyCharacter key)
{
   CEGUI::System::getSingleton().injectKeyDown( static_cast<CEGUI::uint>(key) );
   CEGUI::System::getSingleton().injectChar( static_cast<CEGUI::utf32>( key ) );
}

void Injector::keyRelease(Producer::KeyCharacter key)
{
   CEGUI::System::getSingleton().injectKeyUp( static_cast<CEGUI::uint>(key) );
}

void Injector::specialKeyPress(Producer::KeyCharacter prkey)
{
   KeyMap::iterator iter = _keys.find( prkey );
   if( iter != _keys.end() )
   {
      CEGUI::Key::Scan cekey = (*iter).second;
      CEGUI::System::getSingleton().injectKeyDown( static_cast<CEGUI::uint>(cekey) );
   }
   CEGUI::System::getSingleton().injectChar( static_cast<CEGUI::utf32>( prkey ) );
}

void Injector::specialKeyRelease(Producer::KeyCharacter prkey)
{
   KeyMap::iterator iter = _keys.find( prkey );
   if( iter != _keys.end() )
   {
      CEGUI::Key::Scan cekey = (*iter).second;
      CEGUI::System::getSingleton().injectKeyUp( static_cast<CEGUI::uint>(cekey) );
   }
}

Using it all together

Here is what a simple main() will look like:

int main(unsigned int argc, char* argv[])
{
  if( argc < 4 )
  {
    PrintUsage();
    return 1;
  }

  Producer::ref_ptr<example::Application> app = new example::Application(argv[1]);
  app->GetViewer()->GetRenderSurface()->realize();

  app->LoadCEGUIScheme( argv[2] );
  app->LoadCEGUILayout( argv[3] );

  while( !app->IsFinished() )
  {
    app->GetViewer()->GetCamera()->frame();
  }

  return 0;
}