Difference between revisions of "Using CEGUI with GLUT"
Jim Storch (Talk | contribs) (→GLUT's Keyboard Callbacks) |
Jim Storch (Talk | contribs) (→The New Loop) |
||
Line 730: | Line 730: | ||
==The New Loop== | ==The New Loop== | ||
− | while(keep_running) | + | while( keep_running ) |
− | + | { | |
− | + | glutMainLoopEvent(); | |
− | + | render(); | |
− | + | CEGUI::System::getSingleton().renderGUI(); | |
− | + | glutSwapBuffers(); | |
− | + | } | |
− | + | ||
==Makefile for GLUT + CEGUI== | ==Makefile for GLUT + CEGUI== |
Revision as of 16:08, 15 December 2006
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.
Contents
- 1 The Loop
- 2 Makefile for GLUT Only
- 3 GLUT's Keyboard Callbacks
- 4 Connecting GLUT's Keyboard Callbacks to CEGUI
- 5 GLUT's Mouse Callbacks
- 6 Connecting GLUT's Mouse Callbacks to CEGUI
- 7 Registering the Callbacks
- 8 Creating a Renderer
- 9 The New Loop
- 10 Makefile for GLUT + CEGUI
- 11 License for the Code Samples
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.