How to use CEGUI with SDL and OpenGL
Written for CEGUI 0.7.5
Works with versions 0.7.5.x (obsolete)
This tiny HOW-To simply illustrates with a full, runnable example, how CEGUI can be used with SDL and OpenGL.
It shows:
- a minimal example
- a more involved example that includes most of the CEGUI widgets that are available (with the Taharez Look)
The platform used here is GNU/Linux.
Contents
Common Topics
Prerequisites
CEGUI depends on various libraries. We used:
- FreeImage [3.15.0] ; you must specify one library among FreeImage, DevIL or SILLY (this is their usual order in terms of preference), otherwise only TGA images could be loaded (note: FreeImage install must be fixed if targeting a prefixed directory rather than the default one in the system tree; anyway pre-0.8 versions of CEGUI's will need to have FreeImage installed in the system)
- PCRE [8.12] ; if building if from sources, specify the following configure option:
--enable-unicode-properties
- FreeType [2.4.3]
If you installed PCRE or FreeType in a prefixed directory, prior to executing CEGUI's configure you should specify in your shell respectively:
export pcre_CFLAGS="-I${pcre_PREFIX}/include"
export pcre_LIBS="-L${pcre_PREFIX}/lib -lpcre"
and
export freetype2_CFLAGS="-I${freetype_PREFIX}/include -I${freetype_PREFIX}/include/freetype2"
export freetype2_LIBS="-L${freetype_PREFIX}/lib -lfreetype"
We used SDL version 1.2.14.
Finally, GLUT is needed for the CEGUI's samples, Ubuntu users may thus execute beforehand, as root:
apt-get install libglut3-dev libglut3
For CEGUI itself, we used the following configuration options: --enable-debug --disable-lua-module --disable-python-module
Locating Resources
For default CEGUI's resources to be found (ex: fonts, images, etc.), the test program must be able to find out what are their paths, which are relative to the place where CEGUI was installed. One must thus update accordingly in the example the CEGUIInstallBasePath
variable so that it points to a directory from which /share/CEGUI/
can be found.
Example:
const std::string & CEGUIInstallBasePath = "/home/joe/my-CEGUI-0.7.5-install" ;
Building The Examples
A simple yet powerful enough makefile could be this one (ex: save it under the "GNUmakefile" filename ; all our prerequisites are installed in a directory pointed to by the LOANI_INSTALLATIONS environment variable):
CEGUI_INSTALL_ROOT := $$LOANI_INSTALLATIONS/CEGUI-0.7.5 SDL_INSTALL_ROOT := $$LOANI_INSTALLATIONS/SDL-1.2.14 SDL_CONFIG := $(SDL_INSTALL_ROOT)/bin/sdl-config C_COMPILER := gcc CPP_COMPILER := g++ INC_FLAGS = -I$(CEGUI_INSTALL_ROOT)/include/CEGUI `$(SDL_CONFIG) --cflags` LD_FLAGS = -L$(CEGUI_INSTALL_ROOT)/lib -lCEGUIOpenGLRenderer -lCEGUIBase `$(SDL_CONFIG) --libs` EXECUTABLES := $(patsubst %.cc,%.exe,$(wildcard *.cc)) # Note: source $LOANI_INSTALLATIONS/OSDL-environment.sh before running produced # executables. all: $(EXECUTABLES) %.o: %.c @echo " Compiling $@" $(C_COMPILER) -c $< -o $@ $(INC_FLAGS) %.o: %.cc @echo " Compiling $@" $(CPP_COMPILER) -c $< -o $@ $(INC_FLAGS) %.exe: %.o %.c @echo " Linking $@" $(C_COMPILER) -o $@ $< $(LD_FLAGS) %.exe: %.o %.cc @echo " Linking $@" $(CPP_COMPILER) -o $@ $< $(LD_FLAGS) clean: @echo " Cleaning" -@/bin/rm -f *.o $(EXECUTABLES) infos: @echo "CEGUI_INSTALL_ROOT = $(CEGUI_INSTALL_ROOT)" @echo "INC_FLAGS = $(INC_FLAGS)" @echo "LD_FLAGS = $(LD_FLAGS)" @echo "EXECUTABLES = $(EXECUTABLES)"
The main point is to set correctly the compilation and linking flags for SDL and CEGUI.
Then the build should be as easy as:
$ make clean all
Cleaning Compiling CEGUI-SDL-all-widgets.o
g++ -c CEGUI-SDL-all-widgets.cc -o CEGUI-SDL-all-widgets.o -I$LOANI_INSTALLATIONS/CEGUI-0.7.5/include/CEGUI `$LOANI_INSTALLATIONS/SDL-1.2.14/bin/sdl-config --cflags`
Linking CEGUI-SDL-all-widgets.exe
g++ -o CEGUI-SDL-all-widgets.exe CEGUI-SDL-all-widgets.o -L$LOANI_INSTALLATIONS/CEGUI-0.7.5/lib -lCEGUIOpenGLRenderer -lCEGUIBase `$LOANI_INSTALLATIONS/SDL-1.2.14/bin/sdl-config --libs`
Compiling CEGUI-SDL-hello-world.o
g++ -c CEGUI-SDL-hello-world.cc -o CEGUI-SDL-hello-world.o -I$LOANI_INSTALLATIONS/CEGUI-0.7.5/include/CEGUI `$LOANI_INSTALLATIONS/SDL-1.2.14/bin/sdl-config --cflags`
Linking CEGUI-SDL-hello-world.exe
g++ -o CEGUI-SDL-hello-world.exe CEGUI-SDL-hello-world.o -L$LOANI_INSTALLATIONS/CEGUI-0.7.5/lib -lCEGUIOpenGLRenderer -lCEGUIBase `$LOANI_INSTALLATIONS/SDL-1.2.14/bin/sdl-config --libs` rm CEGUI-SDL-all-widgets.o CEGUI-SDL-hello-world.o
A Minimal Example
As you can see in this file (ex: to be saved as CEGUI-SDL-hello-world.cc), it is quite short and easy to understand:
/* * This is a rather minimal 'hello world' test of CEGUI, used we SDL and OpenGL, * on GNU/Linux. * * Largely inspired from * http://www.cegui.org.uk/wiki/index.php/Using_CEGUI_with_SDL_and_OpenGL_%280.7%29 * * Author: Olivier Boudeville (olivier.boudeville@esperide.com) * */ #include "SDL.h" #include "CEGUI.h" #include "RendererModules/OpenGL/CEGUIOpenGLRenderer.h" #include <GL/gl.h> #include <iostream> using namespace std ; using namespace CEGUI ; // Change according to your installation path: const std::string & CEGUIInstallBasePath = "/home/wondersye/Projects/LOANI-latest/LOANI-installations/CEGUI-0.7.5" ; /* const std::string & CEGUIInstallBasePath = "/usr" ; */ // Input management: from SDL to CEGUI. void handle_mouse_down( Uint8 button ) { switch ( button ) { case SDL_BUTTON_LEFT: CEGUI::System::getSingleton().injectMouseButtonDown( CEGUI::LeftButton ) ; break ; case SDL_BUTTON_MIDDLE: CEGUI::System::getSingleton().injectMouseButtonDown( CEGUI::MiddleButton ) ; break ; case SDL_BUTTON_RIGHT: CEGUI::System::getSingleton().injectMouseButtonDown( CEGUI::RightButton) ; break ; case SDL_BUTTON_WHEELDOWN: CEGUI::System::getSingleton().injectMouseWheelChange( -1 ) ; break ; case SDL_BUTTON_WHEELUP: CEGUI::System::getSingleton().injectMouseWheelChange( +1 ) ; break ; default: cout << "handle_mouse_down ignored '" << static_cast<int>( button ) << "'" << endl ; break ; } } void handle_mouse_up( Uint8 button ) { switch ( button ) { case SDL_BUTTON_LEFT: CEGUI::System::getSingleton().injectMouseButtonUp( CEGUI::LeftButton ) ; break ; case SDL_BUTTON_MIDDLE: CEGUI::System::getSingleton().injectMouseButtonUp( CEGUI::MiddleButton ) ; break ; case SDL_BUTTON_RIGHT: CEGUI::System::getSingleton().injectMouseButtonUp( CEGUI::RightButton ) ; break ; case SDL_BUTTON_WHEELDOWN: break ; case SDL_BUTTON_WHEELUP: break ; default: cout << "handle_mouse_up ignored '" << static_cast<int>( button ) << "'" << endl ; break ; } } void inject_input( bool & must_quit ) { SDL_Event e ; // Go through all available events: while ( SDL_PollEvent( &e ) ) { // Route according to the event type: switch( e.type ) { // Mouse section: case SDL_MOUSEMOTION: // We inject the mouse position directly here: CEGUI::System::getSingleton().injectMousePosition( static_cast<float>( e.motion.x ), static_cast<float>( e.motion.y ) ) ; break ; case SDL_MOUSEBUTTONDOWN: handle_mouse_down( e.button.button ) ; break ; case SDL_MOUSEBUTTONUP: handle_mouse_up( e.button.button ) ; break ; // Keyboard section: case SDL_KEYDOWN: CEGUI::System::getSingleton().injectKeyDown(e.key.keysym.scancode) ; /* * Managing the character is more difficult, we have to use a translated * unicode value: * */ if ( (e.key.keysym.unicode & 0xFF80) == 0 ) { CEGUI::System::getSingleton().injectChar( e.key.keysym.unicode & 0x7F ) ; } break ; case SDL_KEYUP: CEGUI::System::getSingleton().injectKeyUp( e.key.keysym.scancode ) ; break ; // A WM quit event occured: case SDL_QUIT: must_quit = true ; break ; case SDL_VIDEORESIZE: CEGUI::System::getSingleton().notifyDisplaySizeChanged( CEGUI::Size( e.resize.w, e.resize.h ) ) ; break ; } } } void inject_time_pulse( double & last_time_pulse ) { // Get current "run-time" in seconds: double current_time_pulse = 0.001 * SDL_GetTicks() ; // Inject the time that passed since the last call: CEGUI::System::getSingleton().injectTimePulse( static_cast<float>( current_time_pulse - last_time_pulse ) ) ; // Records the new time as the last time: last_time_pulse = current_time_pulse ; } void render_gui() { // Clears the colour buffer: glClear( GL_COLOR_BUFFER_BIT ) ; // Renders the GUI: CEGUI::System::getSingleton().renderGUI() ; // Updates the screen: SDL_GL_SwapBuffers() ; } void main_loop () { cout << " - entering main loop" << endl ; bool must_quit = false ; // get "run-time" in seconds double last_time_pulse = 0.001 * static_cast<double>( SDL_GetTicks() ) ; while ( ! must_quit ) { inject_input( must_quit ) ; inject_time_pulse( last_time_pulse ) ; render_gui() ; } cout << " - leaving main loop" << endl ; } SDL_Surface & init_SDL() { cout << " - initializing SDL" << endl ; atexit (SDL_Quit) ; if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) { cerr << "Unable to initialise SDL: " << SDL_GetError() ; exit(0) ; } SDL_Surface * screen = SDL_SetVideoMode ( 600, 480, 0, SDL_OPENGL ) ; if ( screen == 0 ) { cerr << "Unable to set OpenGL videomode: " << SDL_GetError() ; SDL_Quit() ; exit(0) ; } SDL_ShowCursor( SDL_DISABLE ) ; SDL_EnableUNICODE( 1 ) ; SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL ) ; return *screen ; } void set_CEGUI_paths() { // Initialises the required directories for the DefaultResourceProvider: CEGUI::DefaultResourceProvider & defaultResProvider = * static_cast<CEGUI::DefaultResourceProvider*> ( CEGUI::System::getSingleton().getResourceProvider() ) ; const string CEGUIInstallSharePath = CEGUIInstallBasePath + "/share/CEGUI/" ; // For each resource type, sets a corresponding resource group directory: cout << "Using scheme directory '" << CEGUIInstallSharePath + "schemes/" << "'" << endl ; defaultResProvider.setResourceGroupDirectory( "schemes", CEGUIInstallSharePath + "schemes/" ) ; defaultResProvider.setResourceGroupDirectory( "imagesets", CEGUIInstallSharePath + "imagesets/" ) ; defaultResProvider.setResourceGroupDirectory( "fonts", CEGUIInstallSharePath + "fonts/" ) ; defaultResProvider.setResourceGroupDirectory( "layouts", CEGUIInstallSharePath + "layouts/" ) ; defaultResProvider.setResourceGroupDirectory( "looknfeels", CEGUIInstallSharePath + "looknfeel/" ) ; defaultResProvider.setResourceGroupDirectory( "lua_scripts", CEGUIInstallSharePath + "lua_scripts/" ) ; defaultResProvider.setResourceGroupDirectory( "schemas", CEGUIInstallSharePath + "xml_schemas/" ) ; defaultResProvider.setResourceGroupDirectory( "animations", CEGUIInstallSharePath + "animations/" ) ; // Sets the default resource groups to be used: CEGUI::Imageset::setDefaultResourceGroup( "imagesets" ) ; CEGUI::Font::setDefaultResourceGroup( "fonts" ) ; CEGUI::Scheme::setDefaultResourceGroup( "schemes" ) ; CEGUI::WidgetLookManager::setDefaultResourceGroup( "looknfeels" ) ; CEGUI::WindowManager::setDefaultResourceGroup( "layouts" ) ; CEGUI::ScriptModule::setDefaultResourceGroup( "lua_scripts" ) ; CEGUI::AnimationManager::setDefaultResourceGroup( "animations" ) ; // Set-up default group for validation schemas: CEGUI::XMLParser * parser = CEGUI::System::getSingleton().getXMLParser() ; if ( parser->isPropertyPresent( "SchemaDefaultResourceGroup" ) ) parser->setProperty( "SchemaDefaultResourceGroup", "schemas" ) ; } WindowManager & init_CEGUI( SDL_Surface & surface ) { cout << " - initializing CEGUI" << endl ; CEGUI::OpenGLRenderer::bootstrapSystem() ; set_CEGUI_paths() ; SchemeManager::getSingleton().create( "TaharezLook.scheme" ) ; System::getSingleton().setDefaultMouseCursor( "TaharezLook", "MouseArrow" ) ; return WindowManager::getSingleton() ; } void create_gui( WindowManager & winManager ) { cout << " - creating the GUI" << endl ; DefaultWindow & rootWin = * static_cast<DefaultWindow*>( winManager.createWindow( "DefaultWindow", "Root" ) ) ; System::getSingleton().setGUISheet( &rootWin ) ; FrameWindow & myWin = * static_cast<FrameWindow*>( winManager.createWindow( "TaharezLook/FrameWindow", "Demo Window" ) ) ; rootWin.addChildWindow( &myWin ) ; myWin.setPosition( UVector2( cegui_reldim(0.25f), cegui_reldim(0.25f) ) ) ; myWin.setSize( UVector2( cegui_reldim(0.5f), cegui_reldim(0.5f) ) ) ; myWin.setMaxSize( UVector2( cegui_reldim(1.0f), cegui_reldim(1.0f) ) ) ; myWin.setMinSize( UVector2( cegui_reldim(0.1f), cegui_reldim(0.1f) ) ) ; myWin.setText( "Hello World! This is a minimal SDL+OpenGL+CEGUI test." ) ; } int main( int argc, char *argv[] ) { cout << " - starting CEGUI test" << endl ; SDL_Surface & screen = init_SDL() ; WindowManager & winManager = init_CEGUI( screen ) ; create_gui( winManager ) ; main_loop() ; cout << " - ending CEGUI test" << endl ; }
The point is first to initialize correctly SDL (notably for OpenGL and input settings) and CEGUI (mainly with regard to resource paths).
Then the application-specific GUI itself is to be created, here fully from code (rather than thanks to XML description files).
The main loop can then be run, it just iterates endlessly through the following steps:
- inputs (including wallclock-timing) are collected from SDL and then fed appropriately to CEGUI
- GUI is requested to update its rendering accordingly
Here is a corresponding screenshot:
An Example With Most Widgets
The purpose of this example is to show most CEGUI widgets in the Taharez Look, in order for a developer to know what are the GUI elements that he can use. No wiring or event processing is done for them, we just want to showcase the available widget toolbox.
This more complex example is simply built on the minimal one.
Here is a corresponding screenshot: