Irrlicht Manual Chapter 3

<< Chapter 2 Back To Index Chapter 4 >>

Chapter 3: User Interface

Table of Contents

  1. Introduction
  2. Basic Gui Functions
  3. Gui List Box
  4. The Scroll Bar And File Opening
  5. Transparency and Buttons
  6. Skins and Images
  7. Event Receiver For a GUI
  8. Making a Gui
  9. Gui Explained
  10. Chapter Conclusion

3.1 Introduction

The GUI is what allows you to show and operate all (well, most of) the ordinary controls of a windows/KDE/MacOS/whatever graphical application out there, e.g. buttons, textboxes, listboxes, menus, spin controls etc. Having an integrated GUI system gives many benefits:

  • You don't have to struggle with wxWidgets, or Qt, GTK, or any other widget library out there;
  • Less dependencies and easier ports of your application: just need the Irrlicht engine ported on your target platform and you are set;
  • A uniform look and feel for your app throughout platforms;
  • Faster response time and less prone to tangle somewhere down the system message queues.

If you think that a videogame does not really need a GUI after all, think twice. Any game has some preference panes, a paperdoll, a HUD or some kind of 2D display over the 3D graph; lest YOUR game be a simulator (races, combat, flight) you will end up with lots and lots of commands and settings, definitely more than you can put onscreen. Moreover, who said that Irrlicht can do only games? 3D modelers, CAD/CAM software, FEM analysis, scientific and medical imaging. There are many fields where a 3D engine comes handy. And all of them will definitely need a good GUI.

3.2 Basic Gui Functions

All of the GUI stuff spawns from one class, the IGUIEnvironment, which is the brain of the GUI. Its factory methods allow you to create any control you need, assign it an ID and a position onscreen; you can even retrieve those controls just querying your instance of IGUIEnvironment to get a pointer to them.

First things first, you will need to get it from the IrrlichtDevice:

 IGUIEnvironment* myEnv = myDevice->getGUIEnvironment();

Then you set up your basic GUI, putting in it any control you need, with a pretty clean init function:

 myApp_GUIbase_init(myEnv);  // This is not part of Irrlicht, it's YOUR task: we delve into it later

This can be the starting screen of your game, your HUD/console or a system menu with a toolbar and some buttons: whatever your user need to interact first. From here, you can then spawn every other window, screen or control you want. Finally, you enter the main application loop and draw the GUI on top of the 3D scene:

 while(myDevice->run())
 {
   myDriver->beginScene(true, true, myColor);
   myScene->drawAll();
   myEnv->drawAll();
   myDriver->endScene();
 };   

(Please note: the calling order above is important. You must draw the scene first and then the GUI, or else the scene drawing will mess everything up. Well, now your application touts a fully functional GUI, ready to receive user input. As for mouse, keyboard and joystick, the GUI input too is event-based: you will need to write your own event managers (plural) that deals with GUI-related events)

The other most important parts are, of course, the GUI controls; we will see each of them later in detail, but for now we can examine their base class, IGUIElement. An IGUIElement can be visible or not, enabled or not (boolean attributes), have a screen position, minimum and maximum XY size (int attributes) an alignment etc., all things that you can easily see in the Doxygen-generated documentation, but what really matters is that every control has (well, CAN have) an ID: a number assigned at creation time that can be used later to identify it; this is especially useful for buttons and menu items, since there's not much more to do with them than execute the proper action when the user clicks.

Another important trait is the parenthood relation among controls: each control can have parent (one) and child (many) controls, making a tree structure that simplifies things a lot when grouping many controls in a IGUIWindow to form a dialog. The root of all is the IGUIRootElement, member of IGUIEnvironment, that roots them all. This comes handy managing events: every GUI element has its own (minimal) event receiver that process events originated by himself and its childs, and pass any event that can't or want not to manage to the parent's receiver.

3.3 Event receivers for a GUI

For this reason, multiple GUI event managers is the practical choice here; as a rule of thumb, you will want one event manager for each screen or dialog you create, dealing with events generated from contained controls. Writing a single, soviet-style, grand central GUI event manager is also possible, but you will going to track a lot of miscellanea all at once: it would be an overcomplicated piece of software. Not recommended.

Let's have a look to one: The Single, Soviet-style, Grand Central GUI Event Manager!

 virtual bool MyReceiver::OnEvent(const SEvent& event)
 {
   switch(event.EventType)
   {
     case EET_GUI_EVENT:
     {
       switch(event.GUIEvent.EventType)
       {
         case EGET_SCROLL_BAR_CHANGED: 		return false; break;
         case EGET_BUTTON_CLICKED: 		return false; break;
         case EGET_FILE_SELECTED: 		return false; break;
         case EGET_DIRECTORY_SELECTED:		return false; break;
         case EGET_ELEMENT_FOCUS_LOST: 		return false; break;
         case EGET_ELEMENT_FOCUSED: 		return false; break;
         case EGET_ELEMENT_HOVERED: 		return false; break;
         case EGET_ELEMENT_LEFT: 		return false; break;
         case EGET_ELEMENT_CLOSED: 		return false; break;
         case EGET_CHECKBOX_CHANGED: 		return false; break;
         case EGET_LISTBOX_CHANGED: 		return false; break;
         case EGET_LISTBOX_SELECTED_AGAIN: 	return false; break;
         case EGET_FILE_CHOOSE_DIALOG_CANCELLED:return false; break;
         case EGET_MESSAGEBOX_YES: 		return false; break;
         case EGET_MESSAGEBOX_NO: 		return false; break;
         case EGET_MESSAGEBOX_OK: 		return false; break;
         case EGET_MESSAGEBOX_CANCEL: 		return false; break;
         case EGET_EDITBOX_ENTER: 		return false; break;
         case EGET_EDITBOX_CHANGED: 		return false; break;
         case EGET_EDITBOX_MARKING_CHANGED: 	return false; break;
         case EGET_TAB_CHANGED: 		return false; break;
         case EGET_MENU_ITEM_SELECTED:		return false; break;
         case EGET_COMBO_BOX_CHANGED:		return false; break;
         case EGET_SPINBOX_CHANGED:		return false; break;
         case EGET_TABLE_CHANGED:		return false; break;
         case EGET_TABLE_HEADER_CHANGED:	return false; break;
         case EGET_TABLE_SELECTED_AGAIN:	return false; break;
         case EGET_TREEVIEW_NODE_DESELECT:	return false; break;
         case EGET_TREEVIEW_NODE_SELECT:	return false; break;
         case EGET_TREEVIEW_NODE_EXPAND:	return false; break;
         case EGET_TREEVIEW_NODE_COLLAPSE:      return false; break;
         default: break;
       };
     };
     break;
     default: break;
   };
   return false;
 };

In Irrlicht 1.8.3 there are 32 possible GUI event types from all of the controls implemented. This event manager catches each and every GUI event, but manages none of them. As you already know from other tutorials, returning false means that the event has not been managed and that Irrlicht has to pass it to the next event manager along the chain: this is very important in the GUI event managing. As you will see when we'll talk of windows and dialogs, you may want to return false on some events even when you actually managed them.

Finally, the SGUIEvent struct bears two members, Caller and Element: the first is a pointer to the control which originated the event, the second (if/when not null) points to an element that is related to the former. Both are of type IGUIElement*, but the event type reveals you their real nature, so you can cast it on the fly, e.g.:

 case EGET_EDITBOX_ENTER: // user pressed Enter key in an editbox
   {
   MyApp_doSomethingWithIt(((IGUIEditBox *)event.GUIEvent.Caller)->getText());
   return true; // because we DID manage it!
   };
 break;

Obviously, the place to put our GUI event receiver is the IGUIEnvironment:

 env->setUserEventReceiver(&MyGUIEventReceiver);

WARNING: when Irrlicht sets the device event receiver, it sets the same receiver for both the IrrlichtDevice and the IGUIEnvironment; this means that every time you call (IrrlichtDevice::setEventReceiver()) you will also need to call (IGUIEnvironment::setUserEventReceiver()) to fix things up, because your GUI event manager will be overridden.

3.3.1 Event receiver chaining

As you already know, when an (interesting) event happen, the Irrlicht device fills a SEvent struct and generates its own event. It will then post it through the registered event receivers chain in a predefined order: first of all, it will send the SEvent struct to the receiver registered to the IrrlichtDevice; then, if it returns false, to the IGUIEnvironment receiver. If this too returns false, Irrlicht send it to the main scene manager, which sends it to camera/animators event receivers. If none of them manage it, the event is lost.

GUI events follows another way. They are originated internally: the source is a GUI control, not the OS, and the event is sent first to the very same GUI control that originated it (they have their own receiver, remember?): if it returns false, to the parent's receiver, who does the same with it's parent, and the same all the way down to the IGUIRootElement (whatever it is) and finally the IGUIEnvironment's receiver, had the event not yet been managed.

3.4 The Scroll Bar And File Opening

3.5 Transparency and Buttons

3.6 Skins and Images

3.7 GUI dialog windows

3.8 Making a Gui

3.9 Gui Explained

3.10 Chapter Conclusion

Irrlicht, IrrKlang and Irredit Page Footer