GUI Tutorial

This tutorial will break down the testgui example and explain some of the more interesting features of the dtGUI namespace.

You can find the whole example code in the delta3d/examples/testgui folder.

Below is a screen of the application. You should see the same if you run the application yourself.

testGui.jpg

We need to include some header files to define the classes we'll be using.

#include <dtABC/application.h>
#include <dtCore/object.h>
#include <dtCore/camera.h>
#include <dtCore/deltawin.h>
#include <dtCore/scene.h>
#include <dtCore/globals.h>
#include <dtGUI/scriptmodule.h>
#include <dtGUI/ceuidrawable.h>
#include <dtUtil/log.h>
#include <dtUtil/mathdefines.h>
#include <dtUtil/exception.h>

Now includes the headers for the CEGUI classes we'll be using.

#include <CEGUI/CEGUISchemeManager.h>
#include <CEGUI/CEGUISystem.h>
#include <CEGUI/CEGUIWindowManager.h>
#include <CEGUI/CEGUIWindow.h>
#include <CEGUI/CEGUIExceptions.h>
#include <CEGUI/elements/CEGUIScrollbar.h>
#include <CEGUI/elements/CEGUIPushButton.h>

Lets also tell the compiler about the namespaces we're using so we don't have to type as much.

using namespace dtCore;
using namespace dtABC;
using namespace dtUtil;
using namespace dtGUI;

Now we can create the standard dtABC::Application derivative:

class TestGUIApp : public dtABC::Application
{
public:
   TestGUIApp(const std::string& configFilename = "" ): Application( configFilename ),
      mScriptModule(new ScriptModule())
   {
   }

   virtual ~TestGUIApp()
   {
      mGUI->ShutdownGUI();
      delete mScriptModule;
   }

   virtual void Config()
   {
      dtABC::Application::Config();

Lets add something to the dtCore::Scene to make things interesting. In this case, we'll add a helicopter by creating a new dtCore::Object, loading a 3D file, and adding it to the dtCore::Scene.

      RefPtr<Object> helo = new Object( "Helo" );
      helo->LoadFile( "models/uh-1n.ive" );
      AddDrawable( helo.get() );

We better move our dtCore::Camera around a little to see the Object we just loaded. Here we move it down the negative X axis and rotate the heading clockwise.

      Transform xform(-30.0f, 0.0f, 0.0f, -90.0f, 0.0f, 0.0f );
      GetCamera()->SetTransform( xform );

Since CEGUI will render a cursor, we should probably turn off the rendering of the Operating System cursor. Otherwise we'll see two cursors at once.

      GetWindow()->ShowCursor( false );

Now we can start registering some functions for CEGUI to call. Here, we add two static functions and give them a string "name". We can also use non-static member functions if we want. See the docs for dtGUI::ScriptModule::AddCallback for more information. These functions will now be available for use when we load a CEGUI layout file.

For example, we can have a CEGUI::PushButton call our quitHandler when the button is pressed by adding in the the following line in our .xml Layout file: Event Name="Clicked" Function="quitHandler"

      mScriptModule->AddCallback("quitHandler", &quitHandler );
      mScriptModule->AddCallback("sliderHandler", &sliderHandler );

Finally, can create an instance of the drawable which will actually render the GUI. To do this, we create a new CEGUIDrawable, call a member function to create some GUI, then add it to the dtCore::Scene for rendering. Notice we're passing the dtCore::DeltaWin and the dtGUI::ScriptModule to the CEGUIDrawable's constructor. By supplying the Window, CEGUI will automatically resize its rendering area to match the pixel size of the Window.

         mGUI = new dtGUI::CEUIDrawable(GetWindow(),
                                        GetKeyboard(),
                                        GetMouse(),
                                        mScriptModule);
      }

This is a little method used to store the filename of a CEGUI .xml layout file. The application will load this file if a valid name is supplied.

   void SetLayoutFilename(const std::string& filename)
   {
      mLayoutFilename = filename;
   }

Here we dive into the world of CEGUI. The first thing we need to do is load the CEGUI scheme, default cursor, and font. We absolutely have to have a Scheme file loaded before we can continue.

   void BuildGUI(void)
   {
      try
      {
         std::string schemeFileName = dtCore::FindFileInPathList("gui/schemes/WindowsLook.scheme");

         CEGUI::SchemeManager::getSingleton().loadScheme(schemeFileName);
         CEGUI::System::getSingleton().setDefaultMouseCursor("WindowsLook", "MouseArrow");
         CEGUI::System::getSingleton().setDefaultFont("DejaVuSans-10");
         CEGUI::System::getSingleton().getDefaultFont()->setProperty("PointSize", "20");
Note:
Notice we use the dtCore::FindFileInPathList method to find the .scheme file. This will use the search paths we supplied to find the file.
Now we can create a default, root widget to hold our UI. We'll use the CEGUI::WindowManager to create the wigets for us.
         CEGUI::WindowManager *wm = CEGUI::WindowManager::getSingletonPtr();

         CEGUI::Window* sheet = wm->createWindow("DefaultGUISheet", "root_wnd");

..and once thats been created, lets tell CEGUI to use it.

         CEGUI::System::getSingleton().setGUISheet(sheet);

Since this application can optionally take in an xml Layout file on the command line, lets see if it has been supplied and try to load it. Once its loaded, we can add the returned Window to the default root window we created before.

         if (!mLayoutFilename.empty())
         {
            //load GUI layout from file
            CEGUI::Window *w = wm->loadWindowLayout( mLayoutFilename.c_str() );                 
            sheet->addChildWindow(w);
         }

Otherwise, we'll have to create all the widgets by hand. At this point, you may wish to see the API doc for CEGUI at http://www.cegui.org.uk/api_reference/. Here, we'll create a handfull of widgets, including a Scrollbar and a PushButton which we subscribe to.

         else
         {
            // background panel
            CEGUI::Window* panel = wm->createWindow("WindowsLook/StaticImage", "Panel 1");
            sheet->addChildWindow(panel);
            panel->setPosition(CEGUI::UVector2(cegui_reldim(0.f), cegui_reldim(0.f)));
            panel->setSize(CEGUI::UVector2(cegui_reldim(1.f), cegui_reldim(1.f)));

            //Delta3D text
            CEGUI::Window* st = wm->createWindow("WindowsLook/StaticText","Delta_3D");
            panel->addChildWindow(st);
            st->setPosition(CEGUI::UVector2(cegui_reldim(0.2f), cegui_reldim(0.3f)));
            st->setSize(CEGUI::UVector2(cegui_reldim(0.6f), cegui_reldim(0.2f)));
            st->setText("Delta 3D");
            st->setProperty("FrameEnabled", "false");
            st->setProperty("BackgroundEnabled", "false");
            st->setProperty("HorzFormatting", "HorzCentred");

            // Edit box for text entry
            CEGUI::Window* eb = wm->createWindow("WindowsLook/Editbox", "EditBox");
            panel->addChildWindow(eb);
            eb->setPosition(CEGUI::UVector2(cegui_reldim(0.3f), cegui_reldim(0.55f)));
            eb->setSize(CEGUI::UVector2(cegui_reldim(0.4f), cegui_reldim(0.1f)));
            eb->setText("Editable text box");

            //slider
            CEGUI::Window* slider = wm->createWindow("WindowsLook/HorizontalScrollbar", "slider1");
            panel->addChildWindow(slider);
            slider->setPosition(CEGUI::UVector2(cegui_reldim(0.12f), cegui_reldim(0.1f)));
            slider->setSize(CEGUI::UVector2(cegui_reldim(0.76f), cegui_reldim(0.05f)));
            slider->setProperty("DocumentSize", "100");
            slider->setProperty("PageSize", "16");
            slider->setProperty("StepSize", "1");
            slider->setProperty("OverlapSize", "1");
            slider->setProperty("ScrollPosition", "100");
            slider->subscribeEvent(CEGUI::Scrollbar::EventScrollPositionChanged, &sliderHandler);

            // quit button
            CEGUI::Window* btn = wm->createWindow("WindowsLook/Button", "QuitButton");
            panel->addChildWindow(btn);
            btn->setText("Exit");
            btn->setPosition( CEGUI::UVector2(cegui_reldim(0.4f), cegui_reldim(0.7f)) );
            btn->setSize( CEGUI::UVector2(cegui_reldim(0.2f), cegui_reldim(0.1f)) );
            btn->subscribeEvent(CEGUI::PushButton::EventClicked, &quitHandler);
         }

CEGUI like to throw exceptions when bad things happen. Make sure to catch them!

      // catch to prevent exit (errors will be logged).
      catch(CEGUI::Exception &e)
      {
         Log::GetInstance().LogMessage(Log::LOG_WARNING, __FUNCTION__, 
            "CEGUI::%s", e.getMessage().c_str() );
      }

Here's our quit function. We'll just get the first instance of a dtABC::Application and tell it to quit. Returning true tells CEGUI that we have handled this Event.

   static bool quitHandler(const CEGUI::EventArgs& e)
   {
      dtABC::Application::GetInstance(0)->Quit();
      return true;
   }

This is a little example of how to get the actual widget that triggered the event.

   static bool sliderHandler(const CEGUI::EventArgs& e)
   {
      CEGUI::Scrollbar* slider = (CEGUI::Scrollbar*)((const CEGUI::WindowEventArgs&)e).window;

Once we have the widget, we can do a little math and convert the value of the Scrollbar into a value we can use for alpha coloring.

      float alphaPercent = slider->getScrollPosition()/slider->getDocumentSize();

      //we don't want alpha of 0.0, so map it to something greater.
      float alphaVal = dtUtil::MapRangeValue(alphaPercent, 0.f, 1.f, 0.2f, 1.f);

Lets use this value to set the overall alpha transparency of the GUI. To do that, we'll grab a handle to the highest widget in the hierarchy, and simply set it's alpha.

      CEGUI::Window* sheet = CEGUI::System::getSingleton().getGUISheet();

      if(sheet) sheet->setAlpha(alphaVal);

      return true;
   }
};

Here is the typical Delta3D main:

int main( int argc, const char* argv[] )
{
   //set data search path to find the required Delta3D files and the example data files

   std::string dataPath = dtCore::GetDeltaDataPathList();
   dtCore::SetDataFilePathList(dataPath + ";" +
Note:
Notice we added a search path to the Delta3D/data/ folder. We have to do this to pickup some schema and GUI files. If CEGUI can't find the schema files, exceptions will be thrown all over the place.
Now we simply see if a GUI layout filename was supplied on the command line and save it for later use:
   std::string filename;
   if (argc > 1)
   {
      Log::GetInstance().LogMessage(Log::LOG_ALWAYS, __FUNCTION__,
         "Using GUI file %s...",argv[1]);
      filename = argv[1];
   }

Lets create our cool little application, making sure to store it in a dtCore::RefPtr. We also supply the filename of any command line parameters that might have been set.

   RefPtr<TestGUIApp> app = new TestGUIApp("config.xml");
   app->SetLayoutFilename(filename);

   app->Config(); //configuring the application
   app->Run(); // running the simulation loop

   return 0;
}

http://www.delta3d.org
2.0.0 generated 14 Feb 2008