22 grudnia 2009 Tutorial 5: User Interface



Ten tutorial pokazuje jak używać i budować graficzny interfejs iżytkownika w Silniku Irrlicht. Będzie to krótki ogólny opis i pokaz jak tworzyć i używać okna, przyciski, paski przewijania, teksty statyczne i pola list.

Jak zawsze dołączam pliki nagłówkowe, używamy przestrzeni nazw irrlicht. Przechowujemy także wskaźnik w urządzeniu Irrlicht, zmienny licznik do zmian pozycji okna i wskaźnika do pola listy.

         
#include <irrlicht.h>
#include <iostream>

using namespace irr;

using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

#ifdef _IRR_WINDOWS_
#pragma comment(lib, "Irrlicht.lib")
#endif

// Declare a structure to hold some context for the event receiver so that it
// has it available inside its OnEvent() method.
struct SAppContext
{
        IrrlichtDevice *device;
        s32                             counter;
        IGUIListBox*    listbox;
};

// Define some values that we'll use to identify individual GUI controls.
enum
{
        GUI_ID_QUIT_BUTTON = 101,
        GUI_ID_NEW_WINDOW_BUTTON,
        GUI_ID_FILE_OPEN_BUTTON,
        GUI_ID_TRANSPARENCY_SCROLL_BAR
};

   

Event Receiver jest zdolny nie tylko do przyjmowania zdarzeń z klawiatury i myszy, ale również zdarzeń graficznego interfejsu użytkownika (qui). Zdarzenia są dostępne prawie do wszystkiego: przycisków, zmiany wyboru pola listy, zdarzeń, które informują o najechaniu na element itp. By mieć możliwość reagowania na inne zdarzenia, tworzymy odbieracz zdarzeń. Reagujemy tylko na zdarzenia gui i jeśli to jest takie zdarzenie, bierzemy id z lementu gui, który spowodował zdarzenie i wskaźnik do środowiska gui.
                   
class MyEventReceiver : public IEventReceiver
{
public:
        MyEventReceiver(SAppContext & context) : Context(context) { }

        virtual bool OnEvent(const SEvent& event)
        {
                if (event.EventType == EET_GUI_EVENT)
                {
                        s32 id = event.GUIEvent.Caller->getID();
                        IGUIEnvironment* env = Context.device->getGUIEnvironment();

                        switch(event.GUIEvent.EventType)
                        {
                    
      

Jeśli pasek przewijania zmieni swoją pozycję i jest on 'naszym' paskiem do przewijania (tym z GUI_ID_TRANSPARENCY_SCROLL_BAR id), wtedy zmieniamy przezroczystość wszystkich elementów gui. To jest bardzo proste: Jest skin object, w którym są wszystkie ustawienia dotyczące kolorów, więc po prostu przeglądamy kolory tam umieszczone i zmieniamy ich wartość alpha.
                  
                    
                         case EGET_SCROLL_BAR_CHANGED:
                                if (id == GUI_ID_TRANSPARENCY_SCROLL_BAR)
                                {
                                        s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->
                                        getPos();
                                        
                                        for (u32 i=0; i<EGDC_COUNT ; ++i)
                                        {
                                                SColor col = env->getSkin()->
                                                getColor((EGUI_DEFAULT_COLOR)i);
                                                col.setAlpha(pos);
                                                env->getSkin()->
                                                setColor((EGUI_DEFAULT_COLOR)i, col);
                                        }
                                        
                                }
                                break;
  

Jeżeli przycisk został naciśnięty, może być jednym z 'naszych' trzech przycisków. Jeśli jest to pierwszy, zamkniemy silnik. Jeśli drugi, utworzymy małe okno z tekstem w środku. Dodajemy ciąg znaków do pola listy żeby zobaczyć co się stało. A jeśli jest to trzeci przycisk, to tworzymy okno do wyboru plików i jednocześnie dodajemy ciąg znaków do listy powyżej. To tyle jeżeli chodzi o odbieracz zdarzeń.
                        
                        case EGET_BUTTON_CLICKED:
                                switch(id)
                                {
                                case GUI_ID_QUIT_BUTTON:
                                        Context.device->closeDevice();
                                        return true;

                                case GUI_ID_NEW_WINDOW_BUTTON:
                                        {
                                        Context.listbox->addItem(L"Window created");
                                        Context.counter += 30;
                                        if (Context.counter > 200)
                                                Context.counter = 0;

                                        IGUIWindow* window = env->addWindow(
                                                rect<s32>(100 + Context.counter, 
                                                100 + Context.counter, 
                                                300 + Context.counter, 200 + Context.counter),
                                                false, // modal?
                                                L"Test window");

                                        env->addStaticText(L"Please close me",
                                                rect<s32>(35,35,140,50),
                                                true, // border?
                                                false, // wordwrap?
                                                window);
                                        }
                                        return true;

                                case GUI_ID_FILE_OPEN_BUTTON:
                                        Context.listbox->addItem(L"File open");
                                        env->addFileOpenDialog(L"Please choose a file.");
                                        return true;

                                default:
                                        return false;
                                }
                                break;

                        default:
                                break;
                        }
                }

                return false;
        }

private:
        SAppContext & Context;
};
   

Teraz bardziej interesująca część. Najpierw, tworzymy urządzenie Irrlicht. Tak jak w poprzednich przykładach, pytamy użytkownika którego sterownika chce używać do tego przykładu:
                    
 int main()
{
        // ask user for driver
        
        video::E_DRIVER_TYPE driverType;

        printf("Please select the driver you want for this example:\n"\
                " (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
                " (d) Software Renderer\n (e) Burning's Software Renderer\n"\
                " (f) NullDevice\n (otherKey) exit\n\n");

        char i;
        std::cin >> i;

        switch(i)
        {
                case 'a': driverType = video::EDT_DIRECT3D9;break;
                case 'b': driverType = video::EDT_DIRECT3D8;break;
                case 'c': driverType = video::EDT_OPENGL;   break;
                case 'd': driverType = video::EDT_SOFTWARE; break;
                case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
                case 'f': driverType = video::EDT_NULL;     break;
                default: return 1;
        }       

        // create device and exit if creation failed

        IrrlichtDevice * device = createDevice
        (driverType, core::dimension2d<u32>(640, 480));

        if (device == 0)
                return 1; // could not create selected driver.
    

Proces tworzenia zakończył się sukcesem. Teraz ustawiamy przechwytywacz zdarzeń i zbiór wskaźników dla sterownika i dla środowiska graficznego.
             
        device->setWindowCaption(L"Irrlicht Engine - User Interface Demo");
        device->setResizable(true);

        video::IVideoDriver* driver = device->getVideoDriver();
        IGUIEnvironment* env = device->getGUIEnvironment();
                
                

Aby czcionka stała się troszkę ładniejsza, ładujemy zewnętrzną czcionkę i ustawiamy ją jako nową domyślną czcionkę do skin. Żeby utrzymać standardową czcionkę dla tool tip text, ustawiamy to do czcionki built-in.
        IGUISkin* skin = env->getSkin();
        IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
        if (font)
                skin->setFont(font);

        skin->setFont(env->getBuiltInFont(), EGDF_TOOLTIP);

Dodajemy trzy przyciski. Pierwszy zamyka silnik. Drugi tworzy okno a trzeci otwiera okno do wyboru plików. Trzeci parametr jest id przycisku, z którym łatwo można zidentyfikować przycisk w odbieraczu zdarzeń.
        env->addButton(rect<u32>(10,240,110,240 + 32), 0, GUI_ID_QUIT_BUTTON,
                        L"Quit", L"Exits Program");
        env->addButton(rect<u32>(10,280,110,280 + 32), 0, GUI_ID_NEW_WINDOW_BUTTON,
                        L"New Window", L"Launches a new Window");
        env->addButton(rect<u32>(10,320,110,320 + 32), 0, GUI_ID_FILE_OPEN_BUTTON,
                        L"File Open", L"Opens a file");

Teraz dodajemy statyczny tekst i pasek przewijania, który modyfikuje transparentność wszystkich elementów gui. Ustawiamy maksymalną wartość dla wartości koloru. Wtedy, tworzymy kolejny tekst statyczny i pole listy.
        env->addStaticText(L"Transparent Control:", rect(150,20,350,40), true);
        IGUIScrollBar* scrollbar = env->addScrollBar(true,
                        rect(150, 45, 350, 60), 0, GUI_ID_TRANSPARENCY_SCROLL_BAR);
        scrollbar->setMax(255);

        // set scrollbar position to alpha value of an arbitrary element
        scrollbar->setPos(env->getSkin()->getColor(EGDC_WINDOW).getAlpha());

        env->addStaticText(L"Logging ListBox:", rect<u32>(50,110,250,130), true);
        IGUIListBox * listbox = env->addListBox(rect<u32>(50, 140, 250, 210));
        env->addEditBox(L"Editable Text", rect<u32>(350, 80, 550, 100));

        // Store the appropriate data in a context structure.
        SAppContext context;
        context.device = device;
        context.counter = 0;
        context.listbox = listbox;

        // Then create the event receiver, giving it that context structure.
        MyEventReceiver receiver(context);

        // And tell the device to use our custom event receiver.
        device->setEventReceiver(&receiver);



Wreszcie, tworzymy ładne logo Silnika Irrlicht w górnym lewym rogu.
        env->addImage(driver->getTexture("../../media/irrlichtlogo2.png"),
                        position2d<int>(10,10));


To wszystko, musimy tylko wszystko narysować.
        while(device->run() && driver)
        if (device->isWindowActive())
        {
                driver->beginScene(true, true, SColor(0,200,200,200));

                env->drawAll();
        
                driver->endScene();
        }

        device->drop();

        return 0;
}





PiotrSOG