This tutorial intends to show how to integrate CEGUI with TV3D. This tutorial will be C++ oriented since CEGUI is originally a C++ library but a C# version of the CEGUI library is also available so the main idea could be easily converted.
The following approach will be seperated into 5 sections:
It is possible to create all your widgets with code as well but the .xml approach is used in this tutorial in order to keep it clear and simple.
First, we have to create the header file for our class. Here is an example using the singleton pattern:
TV3DCEGUI.H
#pragma once #include "StdAfx.h" #include <CEGUI.h> #include <CEGUIImageset.h> #include <CEGUISystem.h> #include <CEGUILogger.h> #include <CEGUISchemeManager.h> #include <CEGUIWindowManager.h> #include <CEGUIWindow.h> #include <..\RendererModules\directx9GUIRenderer\d3d9renderer.h> // Might needs to be changed depending on your directory setup class CLTV3DCEGUI { private: CLTV3DCEGUI(void); ~CLTV3DCEGUI(void); public: void InitCEGUI(void); void Render(void); // Get n Set Function LPDIRECT3DDEVICE9 Get3DDevice(void) { return(d3Device); } CEGUI::System* GetGUISystem(void) { return(mGUISystem); } // Singleton Creation and Destruction functions static CLTV3DCEGUI *getInstance () { if (NULL == _singleton) { _singleton = new CLTV3DCEGUI; } return _singleton; } static void kill () { if (NULL != _singleton) { delete _singleton; _singleton = NULL; } } private: static CLTV3DCEGUI *_singleton; CTVInternalObjects* pTVIntObj; // Pointer on TV3D Internal objects LPDIRECT3DDEVICE9 d3Device; // Pointer on Direct3D device CEGUI::Renderer* mGUIRenderer; CEGUI::System* mGUISystem; CEGUI::DefaultWindow* mGUISheet; };
Then we initialize the singleton and setup the constructor and destructor of the class:
TV3DCEGUI.CPP
#include "TV3DCegui.h" // Singleton Initialization to NULL CLTV3DCEGUI *CLTV3DCEGUI::_singleton = NULL; // Constructor CLTV3DCEGUI::CLTV3DCEGUI(void) { d3Device = NULL; pTVIntObj = new CTVInternalObjects(); // We need a pointer on the TV Internal objects to retreive the Direct3D device. InitCEGUI(); // Automatically Init Cegui system when the object is created. } // Destructor CLTV3DCEGUI::~CLTV3DCEGUI(void) { delete(mGUIRenderer); mGUIRenderer = NULL; delete(pTVIntObj); pTVIntObj = NULL; }
CLTV3DCEGUI::InitCEGUI()
// Get Direct3D device from TV3D internal objects d3Device = pTVIntObj->GetDevice3D();
Once the Direct3D device is retreived, we will setup the CEGUI system.
// Init the CEGUI Renderer and System using namespace CEGUI; mGUIRenderer = new DirectX9Renderer(d3Device, 0); mGUISystem = new System(mGUIRenderer); Logger::getSingleton().setLogFilename("cegui.log", true); Logger::getSingleton().setLoggingLevel(CEGUI::Insane);
Still in the InitCEGUI() function, we will load the scheme and the font. We will setup defaults as well.
// Load scheme and set up defaults SchemeManager::getSingleton().loadScheme("GUI\\TaharezLook.scheme"); FontManager::getSingleton().createFont("GUI\\Commonwealth-10.font"); mGUISystem->setDefaultMouseCursor("TaharezLook", "MouseArrow");
and next, we will configure the GUISheet. In CEGUI, everything is considered as a window so we will create a root window where all will be attached to. This window will be without any background and frame so it will be invisible.
So the first step is to created an instance of the WindowManager singleton:
WindowManager& winMgr = WindowManager::getSingleton();
Then we create the GUISheet and set its properties:
mGUISheet = (DefaultWindow*)winMgr.createWindow("TaharezLook/StaticImage", "gui_sheet"); mGUISheet->setPosition(UVector2(UDim(0, 0), UDim(0,0))); // set position mGUISheet->setSize(UVector2(UDim(1, 0), UDim(1,0))); // set size mGUISheet->setProperty("BackgroundEnabled", "false"); // disable standard background mGUISheet->setProperty("FrameEnabled", "false"); // disable frame
And we set our GUISheet to the CEGUI system:
System::getSingleton().setGUISheet(mGUISheet); // install this as the root GUI sheet
In order to allow the possibility to have multiple GUI, we will create another invisible GUIWindow to which we will attached all our widgets. To switch from a GUI to another, you will simply have to set this actual GUIWindow to invisible and set another one with a complete different sets of widgets visible ( Ex: mGUIWnd1, mGUIWnd2, etc...).
Note that we will now load our first layout.xml file. This file can be created with the LayoutEditor provided by CEGUI. This .xml file contains the creation of all widgets and their initial properties(size, location, visible or not, etc...)
Load the layout and attached it to the GUISheet:
// Create a DefaultWindow which all the widgets are attach to. Window* mDefaultGUIWnd = WindowManager::getSingleton().loadWindowLayout("GUI\\YourLayoutFile.layout"); // Attach the Background to the 'real' root mGUISheet->addChildWindow(mDefaultGUIWnd);
At this step, you have a configured GUI System. Let’s continue to render this GUI...
void CLTV3DCEGUI::Render(void) { CEGUI::System::getSingleton().renderGUI(); // CEGUI RENDER // This is needed because CEGUI changes the Renderstate during its render. d3Device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE); // Reverts the RenderState to its original state for TV3D. }
Now that the Render function is created and the GUI is configured, we are ready to display it on the screen and see what it actually looks like.
In your cpp where you have your TV3D render loop, simply include the TVCEGUI.h and in your render loop, you call:
CLTV3DCEGUI::getInstance()->Render();
Now, your GUI should be displayed on the screen but your won’t be able to interact with it until next step...
This is the most complicated step. This could be divided in 3 sub-sections:
In the global section of the TV3DCegui.cpp, you will incorporate a call back function for each type of CEGUI Event. I will show a simple example on how to manage the event launch when the user click on the “X” to close a visible window.
After the destructor in the TV3DCegui.cpp, you insert the callback:
static bool GUI_Callback_WindowClosed(const CEGUI::EventArgs& e) { using namespace CEGUI; // Use CeGUI namespace WindowManager& winMgr = WindowManager::getSingleton(); // we will use the WindowManager to get access to the widgets FrameWindow* wnd = (FrameWindow*) ((const WindowEventArgs&)e).window; // we know it's a window, we Get the window pointer wnd->setProperty("Visible","false"); // Hide the Window return true; }
You can name the callback function whatever you want...
In the TV3DCEGUI.cpp file, you will add a section to your InitGUI() function. For every widget, you will have to link the events you want to manage with the according callback:
// "VisibleWindow" represents the name of the widget that was created within the Layout.xml file. FrameWindow* mVisibleWindow = (FrameWindow*)WindowManager::getSingleton().getWindow("VisibleWindow"); // Here we link the event with the callback for this widget mVisibleWindow->subscribeEvent(FrameWindow::EventCloseClicked, GUI_Callback_WindowClosed);
When this is done, everytime the user clicks on the VisibleWindow Close button, the GUI_Callback_WindowClosed() function will be called. But up to now, CEGUI is not aware of any input event so let’s go forward to the next step...
The purpose of this tutorial is not on how to manage input in tv3d so please refer to the according wiki section for this.
Let’s start by getting a pointer on the TV3DCEGUI singleton:
CLTV3DCEGUI* clCEGUI = CLTV3DCEGUI::GetInstance();
Your own ManageInput section will have to be modified a little... Once the input information is retreived, you need to inject the info to Cegui system. Let’s separate the mouse input and the keyboard input actions to see what needs to be added:
Mouse:
Here you need to retreive the absolute mouse position:
clCEGUI->GetGUISystem()->injectMousePosition(PosX, PosY);
Here you need to test the raisin and fallin edge of the mouse buttons and perform the according action:
clCEGUI->GetGUISystem()->injectMouseButtonDown(CEGUI::LeftButton); // on Button 1 fallin edge clCEGUI->GetGUISystem()->injectMouseButtonUp(CEGUI::LeftButton); // on Button 1 raisin edge clCEGUI->GetGUISystem()->injectMouseButtonDown(CEGUI::RightButton); // on Button 2 fallin edge clCEGUI->GetGUISystem()->injectMouseButtonUp(CEGUI::RightButton); // on Button 2 raisin edge
Keyboard:
For the keyboard data, there are 3 actions that needs to be done. First, you need to inject Keydown. Second, you need to inject Char. Third, you need to inject Keyup.
The second action is a little tricky since the TV_KEY_CODE needs to be translated to a char ASCII code. (Thx to ??? who wrote a solution for this topic on the forum)
In your Input class header, you must add:
private:
byte KEY_PRESSED[256];
HKL keyboardLayout;
In the constructor of your Input class, you must add:
for(int i=0; i<256;i++) { KEY_PRESSED[i] = NULL; } keyboardLayout = GetKeyboardLayout((UINT)0);
To Convert Scancode to ASCII, in your Input class, you must add:
char CLInput::ScancodeToASCII(cCONST_TV_KEY scancode) { char ResultChar = NULL; UINT virtualKey = MapVirtualKeyEx((UINT)scancode, (UINT)1, keyboardLayout); ToAsciiEx(virtualKey, (UINT)scancode, KEY_PRESSED, (LPWORD)&ResultChar, (UINT)0, keyboardLayout); return (ResultChar); }
In your keyboard input management, you must add:
// When you detect a key down: clCEGUI->GetGUISystem()->injectKeyDown(Pressedkey); char c = ScancodeToASCII(Pressedkey); // Transfer to ASCII if (c != (char)0) { clCEGUI->GetGUISystem()->injectChar(c); } // When you detect a key up: clCEGUI->GetGUISystem()->injectKeyUp(Releasedkey);
And somewhere in your loop, you must add:
clCEGUI->GetGUISystem()->injectTimePulse(TimeElapsed);
This is used by the CEGUI System to create fading effect and other feature like this.
Right now, your GUI system should be up and running!