Basic Tutorial 7         CEGUI and Ogre
Tutorial Introduction
Ogre Tutorial Head

In this tutorial we will be exploring how to use CEGUI with Ogre. By the end of this tutorial you should be able to add basic CEGUI functionality to your application. NOTE: This tutorial is not intended to fully teach you how to use CEGUI. This tutorial is intended to get you started. All further CEGUI questions and help should be directed to their home page.

Much deeper and better explained CEGUI tutorials are found in its doxygen documentation with the CEGUI distribution or here on-line

Any problems you encounter during working with this tutorial should be posted in the Help Forum(external link).

Prerequisites

  • This tutorial assumes you have knowledge of C++ programming and are able to set up and compile an Ogre application.
  • This tutorial also assumes that you have created a project using the Ogre Wiki Tutorial Framework, either manually, using CMake or the Ogre AppWizard - see Setting Up An Application for instructions.
  • This tutorial builds on the previous basic tutorials, and it assumes you have already worked through them.

Library versions

  • OGRE: >= 1.7.0
  • CEGUI: >= 0.7.0 and < 0.8.0

NOTE! The API of CEGUI 0.8.x was changed. Most of this tutorial is still valid, but some syntax is different. I have added code where it differs from the 0.7 versions.

You can find the code for this tutorial here. As you go through the tutorial you should be slowly adding code to your own project and watching the results as we build it.

Getting Started

The Initial Code

Modify your Basic Tutorial 7 class header to look like this:

BasicTutorial7 header
#include "BaseApplication.h"

#include <CEGUI/CEGUI.h>
#include <CEGUI/RendererModules/Ogre/CEGUIOgreRenderer.h> //from 0.8 it's just Ogre/Renderer.h

class BasicTutorial7 : public BaseApplication
{
public:
    BasicTutorial7(void);
    virtual ~BasicTutorial7(void);

protected:
    CEGUI::OgreRenderer* mRenderer;

    virtual void createScene(void);

    virtual void createFrameListener(void);

    // Ogre::FrameListener
    virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);

    // OIS::KeyListener
    virtual bool keyPressed( const OIS::KeyEvent &arg );
    virtual bool keyReleased( const OIS::KeyEvent &arg );
    // OIS::MouseListener
    virtual bool mouseMoved( const OIS::MouseEvent &arg );
    virtual bool mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id );
    virtual bool mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id );

    bool quit(const CEGUI::EventArgs &e);
};

Make sure that your Basic Tutorial 7 implementation file looks like this:

BasicTutorial7 implementation
#include "BasicTutorial7.h"

//-------------------------------------------------------------------------------------
BasicTutorial7::BasicTutorial7(void)
{
}
//-------------------------------------------------------------------------------------
BasicTutorial7::~BasicTutorial7(void)
{
}
//-------------------------------------------------------------------------------------
void BasicTutorial7::createScene(void)
{
}
//-------------------------------------------------------------------------------------
void BasicTutorial7::createFrameListener(void)
{
    BaseApplication::createFrameListener();
}
//-------------------------------------------------------------------------------------
bool BasicTutorial7::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
    return BaseApplication::frameRenderingQueued(evt);
}
//-------------------------------------------------------------------------------------
bool BasicTutorial7::keyPressed( const OIS::KeyEvent &arg )
{
    return BaseApplication::keyPressed(arg);
}
//-------------------------------------------------------------------------------------
bool BasicTutorial7::keyReleased( const OIS::KeyEvent &arg )
{
    return BaseApplication::keyReleased(arg);
}
//-------------------------------------------------------------------------------------
bool BasicTutorial7::mouseMoved( const OIS::MouseEvent &arg )
{
    return BaseApplication::mouseMoved(arg);
}
//-------------------------------------------------------------------------------------
bool BasicTutorial7::mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id )
{
    return BaseApplication::mousePressed(arg, id);
}
//-------------------------------------------------------------------------------------
bool BasicTutorial7::mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id )
{
    return BaseApplication::mouseReleased(arg, id);
}
//-------------------------------------------------------------------------------------
bool BasicTutorial7::quit(const CEGUI::EventArgs &e)
{
    return true;
}




#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif

#ifdef __cplusplus
extern "C" {
#endif

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
	INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
	int main(int argc, char *argv[])
#endif
	{
		// Create application object
		BasicTutorial7 app;

		try {
			app.go();
		}
		catch (Ogre::Exception& e) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
			MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
			std::cerr << "An exception has occured: " <<
				e.getFullDescription().c_str() << std::endl;
#endif
		}

		return 0;
	}

#ifdef __cplusplus
}
#endif


CEGUI 0.8.x The second include must be changed to

#include <CEGUI/RendererModules/Ogre/Renderer.h>

Project Settings

Windows Settings

You'll need CEGUI, with the Ogre CEGUI renderer compiled against Ogre 1.7 - i.e. the version of Ogre you're using.
See Building CEGUI for instructions.

Then you need to add the location of the CEGUI headers to your project's Include directories. This code assumes that you've prepared a CEGUI SDK with the following directory layout:

cegui sdk dir layout

After compiling CEGUI, you can copy the files over to make an SDK. Doing so is entirely optional, although the instructions here are based on that layout.

If you don't have your CEGUI headers in a directory called 'CEGUI', just remove the 'CEGUI' bit from the include directives in the Basic Tutorial 7 header.

Project Include Directories

Add the following line to your project's list of Include directories (note that this will vary depending on your CEGUI version):

CEGUI_HOME/include

Project Library Directories

CEGUI_HOME/lib

Project Additional Library Dependencies

Debug
CEGUIBase_d.lib
CEGUIOgreRenderer_d.lib
Release
CEGUIBase.lib
CEGUIOgreRenderer.lib


CEGUI 0.8.x

Release
CEGUIBase-0.lib
CEGUIOgreRenderer-0.lib

Move DLLs

On Windows, you need to copy the following DLLs to your OGRE_HOME/bin/debug directory:

CEGUIBase.dll
CEGUIFalagardWRBase.dll
CEGUIOgreRenderer.dll
CEGUIExpatParser.dll

The last DLL assumes that you're using the default Expat XML parser with CEGUI.

CEGUI 0.8.x

CEGUIBase-0.dll
CEGUIOgreRenderer-0.dll
expat.dll
freetype.dll
pcre.dll

The last three dll's can be found in your build of the CEGUI dependencies

Linux Settings

To build this Tutorial under Linux you need to build and install a recent version of CEGUI and then modify you build to include the CEGUI and CEGUI-OGRE libs. How you do this depends on the type of build you're using. If you're using the autotools stub from here: Setting Up An Application - Autotools - Linux then you need to add the following lines to configure.ac

configure.ac
PKG_CHECK_MODULES(CEGUI, [CEGUI >= 0.7])
AC_SUBST(CEGUI_CFLAGS)
AC_SUBST(CEGUI_LIBS)

PKG_CHECK_MODULES(CEGUI_OGRE, [CEGUI-OGRE >= 0.7])
AC_SUBST(CEGUI_OGRE_CFLAGS)
AC_SUBST(CEGUI_OGRE_LIBS)


and change Makefile.ac thus:

Makefile.am
OgreApp_LDADD= $(OGRE_LIBS) $(OIS_LIBS) $(CEGUI_LIBS) $(CEGUI_OGRE_LIBS)


If you use the CMake build then you need to edit CMakeLists.txt to add those libraries.

I found these links useful Setting Up An Application - Autotools - Linux (particularly the adding more libraries section) and Building CEGUI.

Compile the Code

Be sure you can compile and run this code before continuing. The application should do nothing other than present you with a blank screen (press Escape to exit). If you run into compiler or linker errors, check your settings.

A Brief Introduction To CEGUI

CEGUI is a fully featured GUI library that can be embedded in 3D applications such as Ogre (it also supports OpenGL, DirectX, and Irrlicht backends). Much in the same way that Ogre is only a graphics library (and doesn't do other things such as sound, physics, etc), CEGUI is only a GUI library, meaning it does not do its own rendering nor does it hook into any mouse or keyboard events. In fact, in order for CEGUI to render at all, you have to provide a renderer for it (which is the CEGUIOgreRenderer library shipped with CEGUI), and in order for it to even understand mouse and keyboard events you have to manually inject them into the system. This may seem like a pain at first, but in reality very little code is required to make this happen. It also allows you to have full control over the rendering and the input; CEGUI will never get in the way.

There are many aspects to CEGUI and many quirks that will be unfamiliar to you (even if you have used GUI systems before). I will try to slowly introduce them to you as we go along.

Integrating with Ogre

Defining CEGUI resource groups

CEGUI like Ogre needs diverse types of resources to function. It has several resource managers (like Ogre) which need to find their respective resource locations, so you need to define the necessary resource groups and their locations within resources.cfg.

CEGUI'S resources can be found in different locations depending on your platform. For Linux versions, they usually all get installed into /usr/local/share/CEGUI or /usr/share/CEGUI.

Add the following to resources.cfg - replace 'path_to_cegui' with the path to the CEGUI datafiles:

[Imagesets]
FileSystem=path_to_cegui/imagesets
[Fonts]
FileSystem=path_to_cegui/fonts
[Schemes]
FileSystem=path_to_cegui/schemes
[LookNFeel]
FileSystem=path_to_cegui/looknfeel
[Layouts]
FileSystem=path_to_cegui/layouts

Note that, as is mentioned above, on Ubuntu 10.04 LTS for instance, the path specification "FileSystem=/usr/local/share/CEGUI/xml_schemas" must be added to the [General] section of resources.cfg in order to load the GUI scheme.

Initializing CEGUI

Find the createScene function and add the following code:

mRenderer = &CEGUI::OgreRenderer::bootstrapSystem();

Now that CEGUI has been initialized, we need to set the so-called default resource groups for each of CEGUI'S resource managers.
Add the following:

CEGUI::Imageset::setDefaultResourceGroup("Imagesets");
CEGUI::Font::setDefaultResourceGroup("Fonts");
CEGUI::Scheme::setDefaultResourceGroup("Schemes");
CEGUI::WidgetLookManager::setDefaultResourceGroup("LookNFeel");
CEGUI::WindowManager::setDefaultResourceGroup("Layouts");


CEGUI 0.8.x The first line (Imageset::...) changes to

CEGUI::ImageManager::setImagesetDefaultResourceGroup("Imagesets");


As you can see, we used the resource groups we defined in resources.cfg.

CEGUI is highly customizable, and allows you to define the look and feel of your application by changing its skin (scheme, in terms of CEGUI). We will not be covering how to skin the library in any tutorial, so if you wish to learn more about it, consult the CEGUI website.

The following line of code selects the skin:

CEGUI::SchemeManager::getSingleton().create("TaharezLook.scheme");

The next thing we need to do is set the default mouse cursor:

CEGUI::System::getSingleton().setDefaultMouseCursor("TaharezLook", "MouseArrow");

The first parameter specifies the Imageset and the second one specifies the name of the Image to use from that Imageset.

CEGUI 0.8.x The syntax for setting the image for the mouse is a bit different in 0.8.x:

CEGUI::SchemeManager::getSingleton().createFromFile("TaharezLook.scheme");
CEGUI::System::getSingleton().getDefaultGUIContext().getMouseCursor().setDefaultImage("TaharezLook/MouseArrow");


Throughout this tutorial series we will be using CEGUI to display the mouse cursor, even when we have no other use for the GUI library. It is possible to use another GUI library to render the mouse, or to simply create your own mouse cursor using Ogre directly (though this latter option can be a bit involved). If you are only using CEGUI for the mouse cursor and are concerned about memory usage or the disk space that your game takes up, you can look into one of these options to replace CEGUI.

Lastly note that in that last code snippet we have set the default mouse cursor, but we did not set the mouse cursor directly using the MouseCursor::setImage function as we will in later tutorials. This is because in this tutorial we will always be over some kind of CEGUI window (though it may be invisible), so setting the default cursor will, in effect, make the mouse cursor be the image we selected. If we set the mouse cursor directly and did not set the default, the mouse cursor would be invisible every time it passed over a CEGUI window (which, in this tutorial, will be all the time). On the other hand, setting the default mouse image does nothing if you do not have any CEGUI windows displayed, as will be the case in later tutorials. In that situation, calling MouseCursor::setImage() will display the cursor for the application. Example:

// Do not add this to the program
CEGUI::MouseCursor::getSingleton().setImage( CEGUI::System::getSingleton().getDefaultMouseCursor());

Removing SDKTrays

Before moving on, we need to remove the OgreBites SDKTrays from our application. We do that by overriding two functions: createFrameListener() and frameRenderingQueued().

createFrameListener

Copy the contents of the BaseApplication::createFrameListener function to BasicTutorial7::createFrameListener and delete the bit where SDKTrays is created and the debug overlay set up.
It should look like this when we're done getting rid of it:

void BasicTutorial7::createFrameListener(void)
{
    Ogre::LogManager::getSingletonPtr()->logMessage("*** Initializing OIS ***");
    OIS::ParamList pl;
    size_t windowHnd = 0;
    std::ostringstream windowHndStr;

    mWindow->getCustomAttribute("WINDOW", &windowHnd);
    windowHndStr << windowHnd;
    pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));

    mInputManager = OIS::InputManager::createInputSystem( pl );

    mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject( OIS::OISKeyboard, true ));
    mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject( OIS::OISMouse, true ));

    mMouse->setEventCallback(this);
    mKeyboard->setEventCallback(this);

    //Set initial mouse clipping size
    windowResized(mWindow);

    //Register as a Window listener
    Ogre::WindowEventUtilities::addWindowEventListener(mWindow, this);

    mRoot->addFrameListener(this);
}

frameRenderingQueued

We also need to override BaseApplication::frameRenderingQueued.
Make it look like this:

bool BasicTutorial7::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
    if(mWindow->isClosed())
        return false;

    if(mShutDown)
        return false;

    //Need to capture/update each device
    mKeyboard->capture();
    mMouse->capture();

    //Need to inject timestamps to CEGUI System.
    CEGUI::System::getSingleton().injectTimePulse(evt.timeSinceLastFrame);

    return true;
}

We just removed the mTrayMgr and the mDetailsPanel parts and let CEGUI System know the passing of time.

Injecting Key Events

CEGUI does not handle input in any way. It does not read mouse movements or keyboard input. Instead it relies on the user to inject key and mouse events into the system. The next thing we will need to do is to handle the key events.
If you are working with CEGUI, you will need to have the mouse and keyboard in buffered mode so you can receive the events directly and inject them as they happen.
Find the keyPressed function and add the following code to it (replacing what is there):

CEGUI::System &sys = CEGUI::System::getSingleton();
sys.injectKeyDown(arg.key);
sys.injectChar(arg.text);
return true;


CEGUI 0.8.x The conversion between OIS::Keyevent and CEGUI::Key::Scan is not very clean here. Maybe there is a nicer way, but I don't know it.

CEGUI::GUIContext& context = CEGUI::System::getSingleton().getDefaultGUIContext();
context.injectKeyDown((CEGUI::Key::Scan)arg.key);
context.injectChar((CEGUI::Key::Scan)arg.text);
return true;


After getting the system object, we need to do two things.
The first is to inject the key down event into CEGUI.
The second is to inject the actual character that was pressed.
It is very important to inject the character properly since injecting the key down will not always bring about the desired result when using a non-English keyboard. The injectChar was designed with Unicode support in mind.

Now we need to inject the key up event into the system.
Find the keyReleased function and add the following code (replacing what is there):

CEGUI::System::getSingleton().injectKeyUp(arg.key);
return true;


CEGUI 0.8.x

CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyUp((CEGUI::Key::Scan)arg.key);
return true;


Note that we do not need to inject a character up event, only the key up event is required.

Converting and Injecting Mouse Events

Now that we have finished dealing with keyboard input, we need to take care of mouse input.
We have a small issue that we will need to address, however.
When we injected the key up and down events into CEGUI we never had to convert the key. Both OIS and CEGUI use the same key codes for keyboard input. The same is not true for mouse buttons.
Before we can inject mouse button presses into CEGUI, we will need to write a function which converts OIS button IDs into CEGUI button IDs.
Add the following code to BasicTutorial7.cpp:

CEGUI::MouseButton convertButton(OIS::MouseButtonID buttonID)
{
    switch (buttonID)
    {
    case OIS::MB_Left:
        return CEGUI::LeftButton;

    case OIS::MB_Right:
        return CEGUI::RightButton;

    case OIS::MB_Middle:
        return CEGUI::MiddleButton;

    default:
        return CEGUI::LeftButton;
    }
}

It can stay a local static function, so no need to declare it in the header.

NOTE:
For this to compile properly the function must be declared/defined prior to it's invocation. Be sure to add it to the TOP of the BasicTutorial7.cpp file after the includes.

Now we are ready to inject mouse events.
Find the mousePressed function and add the following code (replacing what is there):

CEGUI::System::getSingleton().injectMouseButtonDown(convertButton(id));
return true;


CEGUI 0.8.x Simply follow the same structure as for the key presses, so add .getDefaultGUIContext() . Also for the next couple of statements.

This should be roughly self-explanatory.
We convert the button ID which was passed in, and pass the result to CEGUI.

Find the mouseReleased function and add this line of code (replacing what is there):

CEGUI::System::getSingleton().injectMouseButtonUp(convertButton(id));
return true;

Lastly, we need to inject mouse motion into CEGUI.
The CEGUI::System object has an injectMouseMove function which expects relative mouse movements.
The OIS::mouseMoved handler gives us those relative movements in the state.X.rel variable and the state.Y.rel variables.
Find the mouseMoved function and add the following code (replacing what is there):

CEGUI::System &sys = CEGUI::System::getSingleton();
sys.injectMouseMove(arg.state.X.rel, arg.state.Y.rel);
// Scroll wheel.
if (arg.state.Z.rel)
    sys.injectMouseWheelChange(arg.state.Z.rel / 120.0f);
return true;

120 is a sort of 'magic number' that was used by Microsoft back in the day, and it's very common in modern systems. OIS uses the same magic number; others, like GLUT, do not. Google it to find out more.

That's it. Now CEGUI is fully set up and receiving mouse and keyboard events. Running it now will already display the custom mouse cursor, but to quit you'll have to press Alt+F4.
Note:
In 0.8.3 version, you can't see the custom mouse cursor until you create and display a sheet (as following).

CEGUI::WindowManager &wmgr = CEGUI::WindowManager::getSingleton();
CEGUI::Window *sheet = wmgr.createWindow("DefaultWindow", "CEGUIDemo/Sheet");
CEGUI::System::getSingleton().getDefaultGUIContext().setRootWindow(sheet);

And another problem, you must move your mouse a bit once, before the cursor becomes visible. Currently, I have no idea about the reason. Glad to know if someone could tell me.

Windows, Sheets, and Widgets

Introduction

CEGUI is very different from most GUI systems. In CEGUI, everything that is displayed is a subclass of the CEGUI::Window class, and a window can have any number of child windows. This means that when you create a frame to contain multiple buttons, that frame is a Window.

This can cause some strange things to happen. You can place a button inside another button, though that would really never happen in practice.

The reason that I mention all of this is when you are looking for a particular widget that you have placed in the application, you need to know they are all called Windows, and are accessed by functions that refer to them as such.

In most practical uses of CEGUI, you will not create each individual object through code. Instead, you create a GUI layout for your application in an editor such as the CEGUI Layout Editor. After placing all of your windows, buttons, and other widgets onto the screen as you like them, the editor saves the layout as a text file. You can later load this layout into what CEGUI calls a GUI sheet (which is also a subclass of CEGUI::Window).

Lastly, know that CEGUI contains a large number of widgets that you can use in your application.
We will not cover them in this tutorial, so if you decide to use CEGUI, be sure to take a good look at their website for more information.

Loading a Sheet

In CEGUI loading a sheet is very easy to do. The WindowManager class provides a "loadWindowLayout" function which loads the sheet and puts it into a CEGUI::Window object.
Then you call CEGUI::System::setGUISheet to display it.
We will not be using this in this tutorial, but I would feel remiss if I did not at least show you an example of its use.

Do not add this to the tutorial (or if you do, remove it after you have seen the results):

// Do not add this to the program
CEGUI::Window *guiRoot = CEGUI::WindowManager::getSingleton().loadWindowLayout("TextDemo.layout"); 
CEGUI::System::getSingleton().setGUISheet(guiRoot);

CEGUI 0.8.x

// Do not add this to the program
CEGUI::Window *guiRoot = CEGUI::WindowManager::getSingleton().loadLayoutFromFile("TextDemo.layout"); 
CEGUI::System::getSingleton().getDefaultGUIContext().setRootWindow(guiRoot);

This sets the sheet currently being displayed.
You can later retrieve this sheet by calling System::getGUISheet.
You can also swap the GUI sheet seamlessly by calling setGUISheet with whatever sheet you want to swap to (though be sure to hold onto a pointer to the current sheet if you wish to swap it back).

Manually creating an Object

As I said before, most of the time you use CEGUI, you will be using GUI sheets that you create using an editor. Occasionally, however, you will need to manually create a widget to put on the screen. In this example, we will be adding a Quit button which we will later add functionality to. Since we will have added more than just the Quit button to the screen by the time the tutorial is over, we need to first create a default CEGUI::Window which will contain all of the widgets we will be creating.

Add this to the end of the createScene function:

CEGUI::WindowManager &wmgr = CEGUI::WindowManager::getSingleton();
CEGUI::Window *sheet = wmgr.createWindow("DefaultWindow", "CEGUIDemo/Sheet");

This uses the WindowManager to create a "DefaultWindow" called "CEGUIDemo/Sheet".
While we could name the window anything we like, it's very common (and encouraged) to name the widget in a hierarchical manner such as "SomeApp/MainMenu/Submenu3/CancelButton".

The next thing we need to do is create the Quit button and set its size:

CEGUI::Window *quit = wmgr.createWindow("TaharezLook/Button", "CEGUIDemo/QuitButton");
quit->setText("Quit");
quit->setSize(CEGUI::UVector2(CEGUI::UDim(0.15, 0), CEGUI::UDim(0.05, 0)));


CEGUI 0.8.x Instead of UVector2, you must use USize

This is very close to being cryptic.

CEGUI uses a "unified dimension" system for its sizes and positions. When setting the size you must create a UDim object to tell it what size it should be.

The first parameter is the relative size of the object in relation to its parent.

The second parameter is the absolute size of the object (in pixels).

The important thing to realize is that you're only supposed to set one of the two parameters of a UDim object; the other parameter must be 0. In this case we have made a button which is 15% as wide as its parent and 5% as tall. If we wanted to specify that it should be 20 pixels by 5 pixels, we would set the second parameter in both of the UDim calls to be 20 and 5 respectively, and the first parameter to be 0.

The last thing we have to do is attach the Quit button to the sheet we have created, and then set the current GUI sheet for the system to be that sheet:

sheet->addChildWindow(quit);
CEGUI::System::getSingleton().setGUISheet(sheet);


CEGUI 0.8.x

sheet->addChild(quit);
CEGUI::System::getSingleton().getDefaultGUIContext().setRootWindow(sheet);


Now if you compile and run your application you will see a Quit button in the top left hand corner of the screen, but it does not yet do anything when you click on it. You can use Alt+F4 to close the window.

Events

Events in CEGUI are very flexible.
Instead of using an interface that you implement to receive events, it uses a callback mechanism which binds any public function (with the appropriate method signature) to be the event handler.
Unfortunately this also means that registering events is a bit more complicated.
We will now register to handle the Quit button's click event to exit the program when it is pressed.
To do that, we will first need a pointer to the Quit button we created in the previous section.

Add the following code to BasicTutorial7::createScene after you have created your quit button window:

quit->subscribeEvent(CEGUI::PushButton::EventClicked, CEGUI::Event::Subscriber(&BasicTutorial7::quit, this));

This will subscribe to the clicked event.
Every widget in CEGUI has a set of events that it supports, and they all begin with "Event".
The first parameter to subscribeEvent is the event itself.
The second parameter is an Event::Subscriber object.
When creating a Subscriber object, the first thing we pass in is a pointer to the function that will handle the event (note the & symbol which gives us the pointer to the function).
The second thing we pass to the Subscriber object is the BasicTutorial7 object which will handle the event (which is the "this" object).
That's it!
Our BasicTutorial7::quit function (which has already been defined) will handle the mouse click and terminate the program.

Add the following code to BasicTutorial7::quit (replacing the contents):

mShutDown = true;
return true;


Compile and run your application to test this out.

One thing to note is that we can create any number of functions to handle events for CEGUI.
The only restrictions on them are they must return a bool, and they must take in a single parameter of type "const CEGUI::EventArgs &".
For more information about events (and how to unsubscribe from them), be sure to read more on the CEGUI website.

Render to Texture


One of the more interesting things we can do with CEGUI is create a render-to-texture window. This allows us to create a second Viewport that can be rendered directly into a CEGUI widget. To do this, we need to start by setting up a scene to look at.

Add the following code to the bottom of the createScene function:

mSceneMgr->setAmbientLight(Ogre::ColourValue(1, 1, 1));
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);
Ogre::Entity* ogreHead = mSceneMgr->createEntity("Head", "ogrehead.mesh");
Ogre::SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(Ogre::Vector3(0, 0, -300));
headNode->attachObject(ogreHead);


Now we must create the RenderTexture.
The RenderSystem object provides the functionality to render to a texture.
To do this we create a texture with the TextureManager::createManual function.

For this program we will create a 512 x 512 texture:

Ogre::TexturePtr tex = mRoot->getTextureManager()->createManual(
    "RTT",
    Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
    Ogre::TEX_TYPE_2D,
    512,
    512,
    0,
    Ogre::PF_R8G8B8,
    Ogre::TU_RENDERTARGET);
Ogre::RenderTexture *rtex = tex->getBuffer()->getRenderTarget();

See the API reference for more information on this function.

Next we need to create a Camera and a Viewport to look at the scene we have created.
Note that we have changed a couple of Viewport options, including turning off Overlays...which is very important to do or you will get CEGUI and Ogre overlays within our mini-window.

Ogre::Camera *cam = mSceneMgr->createCamera("RTTCam");
cam->setPosition(100, -100, -400);
cam->lookAt(0, 0, -300);
Ogre::Viewport *v = rtex->addViewport(cam);
v->setOverlaysEnabled(false);
v->setClearEveryFrame(true);
v->setBackgroundColour(Ogre::ColourValue::Black);

Note that we have added the Viewport to the texture itself (as opposed to the RenderWindow, which is where we usually add Viewports).

Now that we have created our scene and our texture, we need to embed it within CEGUI.
You can create a CEGUI::Texture from any Ogre texture by calling the CEGUI::OgreRenderer::createTexture function:

CEGUI::Texture &guiTex = mRenderer->createTexture(tex);

CEGUI 0.8.x

CEGUI::Texture &guiTex = mRenderer->createTexture("textname", tex);

Unfortunately, this is where things get complicated.
In CEGUI you never just deal with a single Texture or a single image.
CEGUI works with image sets instead of individual images.
It is very useful to work with entire grids of images when you are trying to define the look and feel of a skin you are creating (for example, take a look at TaharezLook.tga in the datafiles/imagesets folder of the CEGUI SDK to see what an image set looks like).
However, even when you are only trying to define a single image, you must create an entire image set for it.



This is what we will be doing:

CEGUI::Imageset &imageSet =
  CEGUI::ImagesetManager::getSingleton().create("RTTImageset", guiTex);
imageSet.defineImage("RTTImage",
                     CEGUI::Point(0.0f, 0.0f),
                     CEGUI::Size(guiTex.getSize().d_width,
                                 guiTex.getSize().d_height),
                     CEGUI::Point(0.0f, 0.0f));

The first line creates the image set (called "RTTImageset") from the texture that we have provided it.
The next line (which calls defineImage), specifies that the first and only image is called "RTTImage" and it is as large as the entire guiTex texture we have provided.

CEGUI 0.8.x The ideas are the same, but the syntax is different. Instead of creating an image set, we now just create an image by using the image manager. The image manager will remember the name "RTTImage".

const CEGUI::Rectf rect(CEGUI::Vector2f(0.0f, 0.0f), guiTex.getOriginalDataSize());
CEGUI::BasicImage* image = (CEGUI::BasicImage*)( &CEGUI::ImageManager::getSingleton().create("BasicImage", "RTTImage"));
   image->setTexture(&guiTex);
   image->setArea(rect);
   image->setAutoScaled(CEGUI::ASM_Both);


Finally we need to create the StaticImage widget which will house the render texture.
The first part is no different from creating any other window:

CEGUI::Window *si = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/StaticImage", "RTTWindow");
//si->setSize(CEGUI::UVector2(CEGUI::UDim(0.5f, 0),
si->setSize(CEGUI::USize(CEGUI::UDim(0.5f, 0),
                            CEGUI::UDim(0.4f, 0)));
si->setPosition(CEGUI::UVector2(CEGUI::UDim(0.5f, 0),
                                CEGUI::UDim(0.0f, 0)));

Now we need to specify which image this StaticImage widget will display. Once again, since CEGUI always deals with image sets and not individual images, we must now retrieve the exact image name from the image set, and display it:

si->setProperty("Image", CEGUI::PropertyHelper::imageToString(&imageSet.getImage("RTTImage")));

If it seems like we have packed a texture into an image set only to unpack it again, it's because that's exactly what we have done.
Manipulating images in CEGUI is not one of the easiest or most straightforward things in the library.

CEGUI 0.8.x makes it a bit easier

si->setProperty("Image", "RTTImage");


The last thing we need to do is add the StaticImage widget to the GUI sheet we created earlier:

sheet->addChildWindow(si);


CEGUI 0.8.x

sheet->addChild(si);


Now we are finished. Compile and run the application. :-)

Image

Conclusion

Now you should have a basic understanding of how to create a simple GUI in Ogre using CEGUI.

Full Source

If you are having difficulty building this tutorial, take a look at the source code for it and compare it to your project.

Alternatives

Here are a few alternatives to CEGUI:

More Information

There are also several other places you can get more information about CEGUI.

Next

Proceed to Basic Tutorial 8 Using Multiple SceneManagers


Alias: Basic_Tutorial_7