Using CEGUI with GLUT

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Revision as of 16:13, 15 December 2006 by Jim Storch (Talk | contribs) (Connecting GLUT's Mouse Callbacks to CEGUI)

Jump to: navigation, search

GLUT is the OpenGL Utility Toolkit. It was designed to make it easy to create simple OpenGL application across different platforms. One reason to use GLUT is you may want a basic life support system while you design your CEGUI code. GLUT lets you get up and running with minimal code and dependencies.

On the other hand, GLUT has a wonky input system that you may find inadequate, especially if your GUI needs exotic keypress combinations or unicode support.

The information presented here is the result of a couple days of tinkering with CEGUI and FreeGLUT on Linux. Let me clearly state that I am an not an expert on CEGUI, GLUT, C++, or Linux and the things I say may not be the optimum approaches. This information is presented with the simple hope that some may find it useful.


The Loop

We're going to specify FreeGLUT instead of GLUT because we need one of the new functions added. In GLUT you call glutMainLoop() and it never comes back. FreeGLUT gives us glutMainLoopEvent which invokes one loop and then returns control. We need this arrangement so we can call CEGUI's renderer later.

This is a simple teapot drawing app which should compile and run provided you have an OpenGL capable display and freeglut + freeglut-devel packages installed.

Note the while(keep_running){} loop.

//glut_loop.cpp
//
#include <GL/freeglut.h>
//
void render(void);
void keyFunc(unsigned char, int, int);
int window_id;
bool keep_running = true;
//
// A barebones GLUT application
//
int main()
    {
    // Create our OpenGL Window
    int fake_argc = 1;
    char* fake_argv;
    glutInit(&fake_argc, &fake_argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowSize(640, 480);
    window_id = glutCreateWindow("GLUT Loop");
    // Set the function to handle normal key presses
    glutKeyboardFunc(keyFunc);
    // Begin the loop
    while(keep_running)
        {
        glutMainLoopEvent();
        render();
        glutSwapBuffers();
        }
    // Exit gracefully
    glutDestroyWindow(window_id);
    return 0;
    }
//
// This is where you'd draw a frame of your 3D application
//
void render()
    {
    glLoadIdentity();
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // Look on my works, ye Mighty, and despair!
    glutWireTeapot( 0.5 );
    }
//
//  Handler for normal keypressed
//
void keyFunc(unsigned char key, int x, int y)
    {
    switch (key)
        {
        case 113:   // 'q',
        case 81:    // 'Q',
        case 27:    // 'ESC'
           keep_running = false;
           break;
        }
    }

Makefile for GLUT Only

Remember, 'make' expects tabs not spaces when indenting compiler commands.

# Makefile to compile glut_loop
glut_loop: glut_loop.o
    g++ glut_loop.o -lglut -o glut_loop
#
glut_loop.o: glut_loop.cpp
    g++ -c glut_loop.cpp
#
.PHONY: clean
clean:
    rm -rf *.o glut_loop

GLUT's Keyboard Callbacks

A note on GLUT's keyboard functions:

Keys that don't generate an ASCII value or aren't in the short list for glutSpecialFunc generate ... nothing. There are no events triggered when pressing the CTRL, ALT, or Shift keys. There is no way to tell if '5' was pressed on the keypad instead of the number keys. Later, I will show you a way to fake CTRL, ALT, and Shift for CEGUI but it wont be in real time.

Despite this, there should be enough utility in them to support a GUI. You can enter text, tab, arrow key about, etc. But if your app needs RIGHT-CRTL for 'fire missles' and Keypad-Slash for 'shields' you'll want another input library.

These are the Set-Handler and the What-a-Handler-Should-Look-Like functions:


void glutKeyboardFunc(myKeyboardFunc)


Set the handler for a normal keypress. These would be keys that generate normal ASCII characters. 'Delete' (ASCII 127) is a normal key. 'Insert' is not. The special keys are handled by glutSpecialFunc (see below).

Example Handler:

  • void myKeyboardFunc(unsigned char key, int x, int y);

You get the ASCII value of the key pressed and the current mouse x and y.


void glutKeyboardUpFunc(myKeyboardUpFunc)


Set the handler for a normal key release.

Example Handler:

  • void myKeyboardUpFunc(unsigned char key, int x, int y)

You get the ASCII value of the key released and the current mouse x and y.


glutSpecialFunc(mySpecialFunc)


Set the handler for a special keypress.

Example Handler:

  • void mySpecialFunc(int key, int x, int y)

You get an int with the special key value and the current mouse x and y. The possible values are:

  • GLUT_KEY_F1 through GLUT_KEY_F12
  • GLUT_KEY_UP, GLUT_KEY_RIGHT, and GLUT_KEY_DOWN, and GLUT_KEY_LEFT
  • GLUT_KEY_PAGE_UP and GLUT_KEY_PAGE_DOWN,
  • GLUT_KEY_HOME, GLUT_KEY_END, and GLUT_KEY_INSERT


glutSpecialUpFunc(mySpecialupFunc)


Sets the handler for a special key release.

Example Handler:

  • void mySpecialUpFunc(int key, int x, int y)

You get an int with the special key value and the current mouse x and y.

Connecting GLUT's Keyboard Callbacks to CEGUI

As you probably know, CEGUI does not capture any input. You need to feed it in it with the various .inject_Something_() functions.


Faking CTRL, ALT, and Shift


As mentioned, GLUT does not generate an event when the user presses or releases these keys. If does have a function called glutGetModifiers() that lets you poll their state. It returns an INT with certain bits set.

First we need to add some global flags to our program:

// Track the status of modifiers
bool ctrl_held = false;
bool alt_held = false;
bool shift_held = false;

Then a function to track the state of these keys:

void fakeCtrlAltShift()
   {
   int status = glutGetModifiers();
   if (status & GLUT_ACTIVE_CTRL)
       {
       if (!ctrl_held)
           {
           ctrl_held = true;
           CEGUI::System::getSingleton().injectKeyDown(CEGUI::Key::LeftControl);
           }
       }
   else
       {
       if (ctrl_held)
           {
           ctrl_held = false;
           CEGUI::System::getSingleton().injectKeyUp(CEGUI::Key::LeftControl);
           }
       }
   //
   if (status & GLUT_ACTIVE_ALT)
       {
       if (!alt_held)
           {
           alt_held = true;
           CEGUI::System::getSingleton().injectKeyDown(CEGUI::Key::LeftAlt);
           }
       }
   else
       {
       if (alt_held)
           {
           alt_held = false;
           CEGUI::System::getSingleton().injectKeyUp(CEGUI::Key::LeftAlt);
           }
       }
   //
   if (status & GLUT_ACTIVE_SHIFT)
       {
       if (!shift_held)
           {
           shift_held = true;
           CEGUI::System::getSingleton().injectKeyDown(CEGUI::Key::LeftShift);
           }
       }
   else
       {
       if (shift_held)
           {
           shift_held = false;
           CEGUI::System::getSingleton().injectKeyUp(CEGUI::Key::LeftShift);
           }
       }
   }

We'll call this in every keyboard handler. It's not pretty but it should keep CEGUI informed.


Scan Codes


For key presses, CEGUI wants to be told the ASCII value of the key pressed and the system scan code generated. For key releases, CEGUI wants the scan code only. Our problem is GLUT has no idea what a scan code is.

Here's two functions to create some fake scan codes, one for normal input and one for the special keys:

// Map ASCII to Scan Codes
int mapNormal(unsigned char c)
   {
   int scancode;
   switch(c)
       {
       case 97:
       case 65:
           scancode = CEGUI::Key::A;
           break;
       case 98:
       case 66:
           scancode = CEGUI::Key::B;
           break;
       case 99:
       case 67:
           scancode = CEGUI::Key::C;
           break;
       case 100:
       case 68:
           scancode = CEGUI::Key::D;
           break;
       case 101:
       case 69:
           scancode = CEGUI::Key::E;
           break;
       case 102:
       case 70:
           scancode = CEGUI::Key::F;
           break;
       case 103:
       case 71:
           scancode = CEGUI::Key::G;
           break;
       case 104:
       case 72:
           scancode = CEGUI::Key::H;
           break;
       case 105:
       case 73:
           scancode = CEGUI::Key::I;
           break;
       case 106:
       case 74:
           scancode = CEGUI::Key::J;
           break;
       case 107:
       case 75:
           scancode = CEGUI::Key::K;
           break;
       case 108:
       case 76:
           scancode = CEGUI::Key::L;
           break;
       case 109:
       case 77:
           scancode = CEGUI::Key::M;
           break;
       case 110:
       case 78:
           scancode = CEGUI::Key::N;
           break;
       case 111:
       case 79:
           scancode = CEGUI::Key::O;
           break;
       case 112:
       case 80:
           scancode = CEGUI::Key::P;
           break;
       case 113:
       case 81:
           scancode = CEGUI::Key::Q;
           break;
       case 114:
       case 82:
           scancode = CEGUI::Key::R;
           break;
       case 115:
       case 83:
           scancode = CEGUI::Key::S;
           break;
       case 116:
       case 84:
           scancode = CEGUI::Key::T;
           break;
       case 117:
       case 85:
           scancode = CEGUI::Key::U;
           break;
       case 118:
       case 86:
           scancode = CEGUI::Key::V;
           break;
       case 119:
       case 87:
           scancode = CEGUI::Key::W;
           break;
       case 120:
       case 88:
           scancode = CEGUI::Key::X;
           break;
       case 121:
       case 89:
           scancode = CEGUI::Key::Y;
           break;
       case 122:
       case 90:
           scancode = CEGUI::Key::Z;
           break;
       case 27:
           scancode = CEGUI::Key::Escape;
           break;
       case 49:
           scancode = CEGUI::Key::One;
           break;
       case 50:
           scancode = CEGUI::Key::Two;
           break;
       case 51:
           scancode = CEGUI::Key::Three;
           break;
       case 52:
           scancode = CEGUI::Key::Four;
           break;
       case 53:
           scancode = CEGUI::Key::Five;
           break;
       case 54:
           scancode = CEGUI::Key::Six;
           break;
       case 55:
           scancode = CEGUI::Key::Seven;
           break;
       case 56:
           scancode = CEGUI::Key::Eight;
           break;
       case 57:
           scancode = CEGUI::Key::Nine;
           break;
       case 48:
           scancode = CEGUI::Key::Zero;
           break;
       case 45:
           scancode = CEGUI::Key::Minus;
           break;
       case 61:
           scancode = CEGUI::Key::Equals;
           break;
       case 8:
           scancode = CEGUI::Key::Backspace;
           break;
       case 9:
           scancode = CEGUI::Key::Tab;
           break;
       case 91:
           scancode = CEGUI::Key::LeftBracket;
           break;
       case 93:
           scancode = CEGUI::Key::RightBracket;
           break;
       case 13:
           scancode = CEGUI::Key::Return;
           break;
       case 59:
           scancode = CEGUI::Key::Semicolon;
           break;
       case 39:
           scancode = CEGUI::Key::Apostrophe;
           break;
       case 96:
           scancode = CEGUI::Key::Grave;
           break;
       case 92:
           scancode = CEGUI::Key::Backslash;
           break;
       case 44:
           scancode = CEGUI::Key::Comma;
           break;
       case 46:
           scancode = CEGUI::Key::Period;
           break;
       case 47:
           scancode = CEGUI::Key::Slash;
           break;
       case 42:
           scancode = CEGUI::Key::Multiply;
           break;
       case 32:
           scancode = CEGUI::Key::Space;
           break;
        case 64:
           scancode = CEGUI::Key::At;
           break;
       case 58:
           scancode = CEGUI::Key::Colon;
           break;
       case 95:
           scancode = CEGUI::Key::Underline;
           break;
       case 127:
           scancode = CEGUI::Key::Delete;
           break;
       default:
           scancode = 0;
       }
   return scancode;
   }


// Map Special GLUT Keys to Scan Codes
int mapSpecial(int c)
   {
   int scancode;
   switch(c)
       {
       case 1:
           scancode = CEGUI::Key::F1;
           break;
       case 2:
           scancode = CEGUI::Key::F2;
           break;
       case 3:
           scancode = CEGUI::Key::F3;
           break;
       case 4:
           scancode = CEGUI::Key::F4;
           break;
       case 5:
           scancode = CEGUI::Key::F5;
           break;
       case 6:
           scancode = CEGUI::Key::F6;
           break;
       case 7:
           scancode = CEGUI::Key::F7;
           break;
       case 8:
           scancode = CEGUI::Key::F8;
           break;
       case 9:
           scancode = CEGUI::Key::F9;
           break;
       case 10:
           scancode = CEGUI::Key::F10;
           break;
        case 11:
           scancode = CEGUI::Key::F11;
           break;
       case 12:
           scancode = CEGUI::Key::F12;
           break;
       case 104:
           scancode = CEGUI::Key::PageUp;
           break;
       case 105:
           scancode = CEGUI::Key::PageDown;
           break;
       case 106:
           scancode = CEGUI::Key::Home;
           break;
       case 107:
           scancode = CEGUI::Key::End;
           break;
       case 100:
           scancode = CEGUI::Key::ArrowLeft;
           break;
       case 101:
           scancode = CEGUI::Key::ArrowUp;
           break;
       case 102:
           scancode = CEGUI::Key::ArrowRight;
           break;
       case 103:
           scancode = CEGUI::Key::ArrowDown;
           break;
       case 108:
           scancode = CEGUI::Key::Insert;
           break;
       default:
           scancode = 0;
       }
   return scancode;
   }


Normal Keypresses


void myKeyboardFunc(unsigned char key, int x, int y)
   {
   fakeCtrlAltShift();
   CEGUI::System::getSingleton().injectChar(key);
   int scancode = mapNormal(key);
   if (scancode)
       {
       CEGUI::System::getSingleton().injectKeyUp(scancode);
       }
   }
void myKeyboardUpFunc(unsigned char key, int x, int y)
   {
   fakeCtrlAltShift();
   int scancode = mapNormal(key);
   if (scancode)
       {
       CEGUI::System::getSingleton().injectKeyUp(scancode);
       }
   }


Special Keypresses


void mySpecialFunc(int key, int x, int y)
   {
   fakeCtrlAltShift();
   int scancode = mapSpecial(key);
   if (scancode)
       {
       CEGUI::System::getSingleton().injectKeyDown(scancode);
       }
   }
void mySpecialUpFunc(int key, int x, int y)
   {
   fakeCtrlAltShift();
   int scancode = mapSpecial(key);
   if (scancode)
       {
       CEGUI::System::getSingleton().injectKeyUp(scancode);
       }
   }


GLUT's Mouse Callbacks

glutMouseFunc(myMouseFunc)


Sets the handler for any mouse button function. This includes presses and releases. Scrolling a mouse wheels generates a press and a release for each tick.

Example Handler:

  • void myMouseFunc(int button, int state, int x, int y)

The button numbering looks like this (depending on your mouse):

  • 0 = Left
  • 1 = Middle
  • 2 = Right
  • 3 = MouseWheel Up
  • 4 = MouseWheel Down

You may notice that the middle and right buttons are swapped from what Windows programmers would expect.

If state == 0, the event is a button press. If state == 1, the event is a button release. X and Y are the current mouse co-ordinates.


glutPassiveMotionFunc(myPassiveMotionFunc)


Set the handler for mouse movement with NO buttons held -- ie not dragging.

Example Handler:

  • void myPassiveMotionFunc(int x, int y)

Pretty simple, x == x, y == y. You will not receive callbacks when the mouse pointer leaves the GLUT window.


glutMotionFunc(myMotionFunc)


Sets the handler for mouse movent with ANY button held -- ie dragging.

Example Handler:

  • void myMotionFunc(int x, int y)

Again x==x, y==y. The behavoir is a little different in that you will get callbacks with a button held and the mouse point dragged out of the GLUT window.

For more exotic callbacks, please see: http://www.opengl.org/resources/libraries/glut/spec3/node45.html


Connecting GLUT's Mouse Callbacks to CEGUI

Handling Buttons


void myMouseFunc(int button, int state, int x, int y)
    {
    if (state == 0)     // State 0 = Button Pressed
        {
        switch (button)
            {
            case 0:     // glut's left mouse button
                CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::LeftButton);
                break;
            case 1:     // glut's middle mouse button
                CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::MiddleButton);
                break;
            case 2:     // glut's right mouse button
                CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::RightButton);
                break;
            case 3:     // glut's mouse wheel up
                CEGUI::System::getSingleton().injectMouseWheelChange(2.0);
                break;
            case 4:     // glut's mouse wheen down
                CEGUI::System::getSingleton().injectMouseWheelChange(-2.0);
                break;
            }
        }
    else if (state == 1)    // State 1 = Button Released
        {
        switch (button)
            {
            case 0:     // glut's left mouse button
                CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::LeftButton);
                break;
            case 1:     // glut's middle mouse button
                CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::MiddleButton);
                break;
            case 2:     // glut's right mouse button
                CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::RightButton);
                break;
            }
        }
    }


Handling Movement


You need both of these, since GLUT uses separate callbacks for normal movement and dragging (or register one function to both callbacks):

void myPassiveMotionFunc(int x, int y)
   {
   CEGUI::System::getSingleton().injectMousePosition(x,y);
   }
void myMotionFunc(int x, int y)
   {
   CEGUI::System::getSingleton().injectMousePosition(x,y);
   }

Registering the Callbacks

In the initialization code of your GLUT program you'll want to add:

// Set some input handlers
glutKeyboardFunc(myKeyboardFunc);              // key pressed
glutKeyboardUpFunc(myKeyboardUpFunc);          // key released
glutSpecialFunc(mySpecialFunc);                // special key pressed
glutSpecialUpFunc(mySpecialUpFunc);            // special key released
glutMouseFunc(myMouseFunc);                    // any mouse button press or release
glutPassiveMotionFunc(myPassiveMotionFunc);    // mouse movement with no buttons held
glutMotionFunc(myMotionFunc);                  // mouse movement with any button held


Creating a Renderer

Naturally, GLUT will use the OpenGLRenderer. So in your CEGUI initialization you'll want:

CEGUI::OpenGLRenderer* myRenderer = new CEGUI::OpenGLRenderer(0);


The New Loop

while( keep_running )
    {
    glutMainLoopEvent();
    render();
    CEGUI::System::getSingleton().renderGUI();
    glutSwapBuffers();
    }

Makefile for GLUT + CEGUI

# Makefile
# compile gui_loop
#
gui_loop: gui_loop.o
   g++ gui_loop.o -lglut -L/usr/local/lib -lCEGUIBase -lCEGUIOpenGLRenderer -o gui_loop
#
gui_loop.o: gui_loop.cpp
   g++ -c gui_loop.cpp -I/usr/local/include/CEGUI
#
.PHONY: clean
clean:
   rm -rf *.o gui_loop


License for the Code Samples

Copyright (C) 2006 Jim Storch

This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.

Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely. Attribution is not required.