Irrlicht 3D Engine
Tutorial 24: CursorControl
024shot.jpg

Show how to modify cursors and offer some useful tool-functions for creating cursors. It can also be used for experiments with the mouse in general.

#include <irrlicht.h>
#include "driverChoice.h"

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

const int DELAY_TIME = 3000;

enum ETimerAction
{
    ETA_MOUSE_VISIBLE,
    ETA_MOUSE_INVISIBLE,
};

Structure to allow delayed execution of some actions.

struct TimerAction
{
    u32 TargetTime;
    ETimerAction Action;
};
struct SAppContext
{
    SAppContext()
    : Device(0), InfoStatic(0), EventBox(0), CursorBox(0), SpriteBox(0)
    , ButtonSetVisible(0), ButtonSetInvisible(0), ButtonSimulateBadFps(0)
    , ButtonChangeIcon(0)
    , SimulateBadFps(false)
    {
    }

    void update()
    {
        if (!Device)
            return;
        u32 timeNow = Device->getTimer()->getTime();
        for ( u32 i=0; i < TimerActions.size(); ++i )
        {
            if ( timeNow >= TimerActions[i].TargetTime )
            {
                runTimerAction(TimerActions[i]);
                TimerActions.erase(i);
            }
            else
            {
                ++i;
            }
        }
    }

    void runTimerAction(const TimerAction& action)
    {
        if (ETA_MOUSE_VISIBLE == action.Action)
        {
            Device->getCursorControl()->setVisible(true);
            ButtonSetVisible->setEnabled(true);
        }
        else if ( ETA_MOUSE_INVISIBLE == action.Action)
        {
            Device->getCursorControl()->setVisible(false);
            ButtonSetInvisible->setEnabled(true);
        }
    }

Add another icon which the user can click and select as cursor later on.

    void addIcon(const stringw& name, const SCursorSprite &sprite, bool addCursor=true)
    {
        // Sprites are just icons - not yet cursors. They can be displayed by Irrlicht sprite functions and be used to create cursors.
        SpriteBox->addItem(name.c_str(), sprite.SpriteId);
        Sprites.push_back(sprite);

        // create the cursor together with the icon?
        if ( addCursor )
        {

Here we create a hardware cursor from a sprite

            Device->getCursorControl()->addIcon(sprite);

            // ... and add it to the cursors selection listbox to the other system cursors.
            CursorBox->addItem(name.c_str());
        }
    }

    IrrlichtDevice * Device;
    gui::IGUIStaticText * InfoStatic;
    gui::IGUIListBox * EventBox;
    gui::IGUIListBox * CursorBox;
    gui::IGUIListBox * SpriteBox;
    gui::IGUIButton * ButtonSetVisible;
    gui::IGUIButton * ButtonSetInvisible;
    gui::IGUIButton * ButtonSimulateBadFps;
    gui::IGUIButton * ButtonChangeIcon;
    array<TimerAction> TimerActions;
    bool SimulateBadFps;
    array<SCursorSprite> Sprites;
};

Helper function to print mouse event names into a stringw

void PrintMouseEventName(const SEvent& event, stringw &result)
{
    switch ( event.MouseInput.Event )
    {
        case EMIE_LMOUSE_PRESSED_DOWN:  result += stringw(L"EMIE_LMOUSE_PRESSED_DOWN"); break;
        case EMIE_RMOUSE_PRESSED_DOWN:  result += stringw(L"EMIE_RMOUSE_PRESSED_DOWN"); break;
        case EMIE_MMOUSE_PRESSED_DOWN:  result += stringw(L"EMIE_MMOUSE_PRESSED_DOWN"); break;
        case EMIE_LMOUSE_LEFT_UP:       result += stringw(L"EMIE_LMOUSE_LEFT_UP"); break;
        case EMIE_RMOUSE_LEFT_UP:       result += stringw(L"EMIE_RMOUSE_LEFT_UP"); break;
        case EMIE_MMOUSE_LEFT_UP:       result += stringw(L"EMIE_MMOUSE_LEFT_UP"); break;
        case EMIE_MOUSE_MOVED:          result += stringw(L"EMIE_MOUSE_MOVED"); break;
        case EMIE_MOUSE_WHEEL:          result += stringw(L"EMIE_MOUSE_WHEEL"); break;
        case EMIE_LMOUSE_DOUBLE_CLICK:  result += stringw(L"EMIE_LMOUSE_DOUBLE_CLICK"); break;
        case EMIE_RMOUSE_DOUBLE_CLICK:  result += stringw(L"EMIE_RMOUSE_DOUBLE_CLICK"); break;
        case EMIE_MMOUSE_DOUBLE_CLICK:  result += stringw(L"EMIE_MMOUSE_DOUBLE_CLICK"); break;
        case EMIE_LMOUSE_TRIPLE_CLICK:  result += stringw(L"EMIE_LMOUSE_TRIPLE_CLICK"); break;
        case EMIE_RMOUSE_TRIPLE_CLICK:  result += stringw(L"EMIE_RMOUSE_TRIPLE_CLICK"); break;
        case EMIE_MMOUSE_TRIPLE_CLICK:  result += stringw(L"EMIE_MMOUSE_TRIPLE_CLICK"); break;
        default:
        break;
    }
}

Helper function to print all the state information which get from a mouse-event into a stringw

void PrintMouseState(const SEvent& event, stringw &result)
{
    result += stringw(L"X: ");
    result += stringw(event.MouseInput.X);
    result += stringw(L"\n");

    result += stringw(L"Y: ");
    result += stringw(event.MouseInput.Y);
    result += stringw(L"\n");


    result += stringw(L"Wheel: ");
    result += stringw(event.MouseInput.Wheel);
    result += stringw(L"\n");

    result += stringw(L"Shift: ");
    if ( event.MouseInput.Shift )
        result += stringw(L"true\n");
    else
        result += stringw(L"false\n");

    result += stringw(L"Control: ");
    if ( event.MouseInput.Control )
        result += stringw(L"true\n");
    else
        result += stringw(L"false\n");

    result += stringw(L"ButtonStates: ");
    result += stringw(event.MouseInput.ButtonStates);
    result += stringw(L"\n");

    result += stringw(L"isLeftPressed: ");
    if ( event.MouseInput.isLeftPressed() )
        result += stringw(L"true\n");
    else
        result += stringw(L"false\n");

    result += stringw(L"isRightPressed: ");
    if ( event.MouseInput.isRightPressed() )
        result += stringw(L"true\n");
    else
        result += stringw(L"false\n");

    result += stringw(L"isMiddlePressed: ");
    if ( event.MouseInput.isMiddlePressed() )
        result += stringw(L"true\n");
    else
        result += stringw(L"false\n");

    result += stringw(L"Event: ");

    PrintMouseEventName(event, result);

    result += stringw(L"\n");
}

A typical event receiver.

class MyEventReceiver : public IEventReceiver
{
public:
    MyEventReceiver(SAppContext & context) : Context(context) { }

    virtual bool OnEvent(const SEvent& event)
    {
        if (event.EventType == EET_GUI_EVENT )
        {
            switch ( event.GUIEvent.EventType )
            {
                case EGET_BUTTON_CLICKED:
                {
                    u32 timeNow = Context.Device->getTimer()->getTime();
                    TimerAction action;
                    action.TargetTime = timeNow + DELAY_TIME;
                    if ( event.GUIEvent.Caller == Context.ButtonSetVisible )
                    {
                        action.Action = ETA_MOUSE_VISIBLE;
                        Context.TimerActions.push_back(action);
                        Context.ButtonSetVisible->setEnabled(false);
                    }
                    else if ( event.GUIEvent.Caller == Context.ButtonSetInvisible )
                    {
                        action.Action = ETA_MOUSE_INVISIBLE;
                        Context.TimerActions.push_back(action);
                        Context.ButtonSetInvisible->setEnabled(false);
                    }
                    else if ( event.GUIEvent.Caller == Context.ButtonSimulateBadFps )
                    {
                        Context.SimulateBadFps = Context.ButtonSimulateBadFps->isPressed();
                    }
                    else if ( event.GUIEvent.Caller == Context.ButtonChangeIcon )
                    {

Replace an existing cursor icon by another icon. The user has to select both - the icon which should be replaced and the icon which will replace it.

                        s32 selectedCursor = Context.CursorBox->getSelected();
                        s32 selectedSprite = Context.SpriteBox->getSelected();
                        if ( selectedCursor >= 0 && selectedSprite >= 0 )
                        {

This does replace the icon.

                            Context.Device->getCursorControl()->changeIcon((ECURSOR_ICON)selectedCursor, Context.Sprites[selectedSprite] );

Do also show the new icon.

                            Context.Device->getCursorControl()->setActiveIcon( ECURSOR_ICON(selectedCursor) );
                        }
                    }
                }
                break;
                case EGET_LISTBOX_CHANGED:
                case EGET_LISTBOX_SELECTED_AGAIN:
                {
                    if ( event.GUIEvent.Caller == Context.CursorBox )
                    {

Find out which cursor the user selected

                        s32 selected = Context.CursorBox->getSelected();
                        if ( selected >= 0 )
                        {

Here we set the new cursor icon which will now be used within our window.

                            Context.Device->getCursorControl()->setActiveIcon( ECURSOR_ICON(selected) );
                        }
                    }
                }
                break;
                default:
                break;
            }
        }

        if (event.EventType == EET_MOUSE_INPUT_EVENT)
        {
            stringw infoText;
            PrintMouseState(event, infoText);
            Context.InfoStatic->setText(infoText.c_str());
            if ( event.MouseInput.Event != EMIE_MOUSE_MOVED && event.MouseInput.Event != EMIE_MOUSE_WHEEL ) // no spam
            {
                infoText = L"";
                PrintMouseEventName(event, infoText);
                Context.EventBox->insertItem(0, infoText.c_str(), -1);
            }
        }

        return false;
    }

private:
    SAppContext & Context;
};

Use several imagefiles as animation frames for a sprite which can be used as cursor icon. The images in those files all need to have the same size. Return sprite index on success or -1 on failure

s32 AddAnimatedIconToSpriteBank( gui::IGUISpriteBank * spriteBank, video::IVideoDriver* driver,  const array< io::path >& files, u32 frameTime )
{
    if ( !spriteBank || !driver || !files.size() )
        return -1;

    video::ITexture * tex = driver->getTexture( files[0] );
    if ( tex )
    {
        array< rect<s32> >& spritePositions = spriteBank->getPositions();
        u32 idxRect = spritePositions.size();
        spritePositions.push_back( rect<s32>(0,0, tex->getSize().Width, tex->getSize().Height) );

        SGUISprite sprite;
        sprite.frameTime = frameTime;

        array< SGUISprite >& sprites = spriteBank->getSprites();
        u32 startIdx = spriteBank->getTextureCount();
        for ( u32 f=0; f < files.size(); ++f )
        {
            tex = driver->getTexture( files[f] );
            if ( tex )
            {
                spriteBank->addTexture( driver->getTexture(files[f]) );
                gui::SGUISpriteFrame frame;
                frame.rectNumber = idxRect;
                frame.textureNumber = startIdx+f;
                sprite.Frames.push_back( frame );
            }
        }

        sprites.push_back( sprite );
        return sprites.size()-1;
    }

    return -1;
}

Use several images within one imagefile as animation frames for a sprite which can be used as cursor icon The sizes of the icons within that file all need to have the same size Return sprite index on success or -1 on failure

s32 AddAnimatedIconToSpriteBank( gui::IGUISpriteBank * spriteBank, video::IVideoDriver* driver,  const io::path& file, const array< rect<s32> >& rects, u32 frameTime )
{
    if ( !spriteBank || !driver || !rects.size() )
        return -1;

    video::ITexture * tex = driver->getTexture( file );
    if ( tex )
    {
        array< rect<s32> >& spritePositions = spriteBank->getPositions();
        u32 idxRect = spritePositions.size();
        u32 idxTex = spriteBank->getTextureCount();
        spriteBank->addTexture( tex );

        SGUISprite sprite;
        sprite.frameTime = frameTime;

        array< SGUISprite >& sprites = spriteBank->getSprites();
        for ( u32 i=0; i < rects.size(); ++i )
        {
            spritePositions.push_back( rects[i] );

            gui::SGUISpriteFrame frame;
            frame.rectNumber = idxRect+i;
            frame.textureNumber = idxTex;
            sprite.Frames.push_back( frame );
        }

        sprites.push_back( sprite );
        return sprites.size()-1;
    }

    return -1;
}

Create a non-animated icon from the given file and position and put it into the spritebank. We can use this icon later on in a cursor.

s32 AddIconToSpriteBank( gui::IGUISpriteBank * spriteBank, video::IVideoDriver* driver,  const io::path& file, const core::rect<s32>& rect )
{
    if ( !spriteBank || !driver )
        return -1;

    video::ITexture * tex = driver->getTexture( file );
    if ( tex )
    {
        core::array< core::rect<irr::s32> >& spritePositions = spriteBank->getPositions();
        spritePositions.push_back( rect );
        array< SGUISprite >& sprites = spriteBank->getSprites();
        spriteBank->addTexture( tex );

        gui::SGUISpriteFrame frame;
        frame.rectNumber = spritePositions.size()-1;
        frame.textureNumber = spriteBank->getTextureCount()-1;

        SGUISprite sprite;
        sprite.frameTime = 0;
        sprite.Frames.push_back( frame );

        sprites.push_back( sprite );

        return sprites.size()-1;
    }

    return -1;
}

int main()
{
    video::E_DRIVER_TYPE driverType = driverChoiceConsole();
    if (driverType==video::EDT_COUNT)
        return 1;

    IrrlichtDevice * device = createDevice(driverType, dimension2d<u32>(640, 480));
    if (device == 0)
        return 1; // could not create selected driver.

    // It's sometimes of interest to know how the mouse behaves after a resize
    device->setResizable(true);

    device->setWindowCaption(L"Cursor control - Irrlicht engine tutorial");
    video::IVideoDriver* driver = device->getVideoDriver();
    IGUIEnvironment* env = device->getGUIEnvironment();

    gui::IGUISpriteBank * SpriteBankIcons;

    SAppContext context;
    context.Device = device;

    rect< s32 > rectInfoStatic(10,10, 200, 200);
    env->addStaticText (L"Cursor state information", rectInfoStatic, true, true);
    rectInfoStatic.UpperLeftCorner += dimension2di(0, 15);
    context.InfoStatic = env->addStaticText (L"", rectInfoStatic, true, true);
    rect< s32 > rectEventBox(10,210, 200, 400);
    env->addStaticText (L"click events (new on top)", rectEventBox, true, true);
    rectEventBox.UpperLeftCorner += dimension2di(0, 15);
    context.EventBox = env->addListBox(rectEventBox);
    rect< s32 > rectCursorBox(210,10, 400, 250);
    env->addStaticText (L"cursors, click to set the active one", rectCursorBox, true, true);
    rectCursorBox.UpperLeftCorner += dimension2di(0, 15);
    context.CursorBox = env->addListBox(rectCursorBox);
    rect< s32 > rectSpriteBox(210,260, 400, 400);
    env->addStaticText (L"sprites", rectSpriteBox, true, true);
    rectSpriteBox.UpperLeftCorner += dimension2di(0, 15);
    context.SpriteBox = env->addListBox(rectSpriteBox);

    context.ButtonSetVisible = env->addButton( rect<s32>( 410, 20, 560, 40 ), 0, -1, L"set visible (delayed)" );
    context.ButtonSetInvisible = env->addButton( rect<s32>( 410, 50, 560, 70 ), 0, -1, L"set invisible (delayed)" );
    context.ButtonSimulateBadFps = env->addButton( rect<s32>( 410, 80, 560, 100 ), 0, -1, L"simulate bad FPS" );
    context.ButtonSimulateBadFps->setIsPushButton(true);
    context.ButtonChangeIcon = env->addButton( rect<s32>( 410, 140, 560, 160 ), 0, -1, L"replace cursor icon\n(cursor+sprite must be selected)" );

    // set the names for all the system cursors
    for ( int i=0; i < (int)gui::ECI_COUNT; ++i )
    {
        context.CursorBox->addItem(stringw( GUICursorIconNames[i] ).c_str());
    }

Create sprites which then can be used as cursor icons.

    SpriteBankIcons = env->addEmptySpriteBank(io::path("cursor_icons"));
    context.SpriteBox->setSpriteBank(SpriteBankIcons);

    // create one animated icon from several files
    array< io::path > files;
    files.push_back( io::path("../../media/icon_crosshairs16x16bw1.png") );
    files.push_back( io::path("../../media/icon_crosshairs16x16bw2.png") );
    files.push_back( io::path("../../media/icon_crosshairs16x16bw3.png") );
    files.push_back( io::path("../../media/icon_crosshairs16x16bw3.png") );
    files.push_back( io::path("../../media/icon_crosshairs16x16bw2.png") );
    SCursorSprite spriteBw; // the sprite + some additional information needed for cursors
    spriteBw.SpriteId = AddAnimatedIconToSpriteBank( SpriteBankIcons, driver, files, 200 );
    spriteBw.SpriteBank = SpriteBankIcons;
    spriteBw.HotSpot = position2d<s32>(7,7);
    context.addIcon(L"crosshair_bw", spriteBw);

    // create one animated icon from one file
    array< rect<s32> > iconRects;
    iconRects.push_back( rect<s32>(0,0, 16, 16) );
    iconRects.push_back( rect<s32>(16,0, 32, 16) );
    iconRects.push_back( rect<s32>(0,16, 16, 32) );
    iconRects.push_back( rect<s32>(0,16, 16, 32) );
    iconRects.push_back( rect<s32>(16,0, 32, 16) );
    SCursorSprite spriteCol;    // the sprite + some additional information needed for cursors
    spriteCol.SpriteId = AddAnimatedIconToSpriteBank( SpriteBankIcons, driver, io::path("../../media/icon_crosshairs16x16col.png"), iconRects, 200 );
    spriteCol.HotSpot = position2d<s32>(7,7);
    spriteCol.SpriteBank = SpriteBankIcons;
    context.addIcon(L"crosshair_colored", spriteCol);

    // Create some non-animated icons
    rect<s32> rectIcon;
    SCursorSprite spriteNonAnimated(SpriteBankIcons, 0, position2d<s32>(7,7));

    rectIcon = rect<s32>(0,0, 16, 16);
    spriteNonAnimated.SpriteId = AddIconToSpriteBank( SpriteBankIcons, driver, io::path("../../media/icon_crosshairs16x16col.png"), rectIcon );
    context.addIcon(L"crosshair_col1", spriteNonAnimated, false);

    rectIcon = rect<s32>(16,0, 32, 16);
    spriteNonAnimated.SpriteId = AddIconToSpriteBank( SpriteBankIcons, driver, io::path("../../media/icon_crosshairs16x16col.png"), rectIcon );
    context.addIcon(L"crosshair_col2", spriteNonAnimated, false);

    rectIcon = rect<s32>(0,16, 16, 32);
    spriteNonAnimated.SpriteId = AddIconToSpriteBank( SpriteBankIcons, driver, io::path("../../media/icon_crosshairs16x16col.png"), rectIcon );
    context.addIcon(L"crosshair_col3", spriteNonAnimated, false);


    MyEventReceiver receiver(context);
    device->setEventReceiver(&receiver);

    while(device->run() && driver)
    {
        // if (device->isWindowActive())
        {
            u32 realTimeNow = device->getTimer()->getRealTime();

            context.update();

            driver->beginScene(true, true, SColor(0,200,200,200));

            env->drawAll();

            // draw custom sprite with Irrlicht functions for comparison. It should usually look the same as the cursors.
            if ( context.SpriteBox )
            {
                s32 selectedSprite = context.SpriteBox->getSelected();
                if ( selectedSprite >= 0 && context.Sprites[selectedSprite].SpriteId >= 0 )
                {
                    SpriteBankIcons->draw2DSprite(u32(context.Sprites[selectedSprite].SpriteId), position2di(580, 140), 0, video::SColor(255, 255, 255, 255), 0, realTimeNow);
                }
            }

            driver->endScene();
        }

        // By simulating bad fps we can find out if hardware-support for cursors works or not. If it works the cursor will move as usual,while it otherwise will just update with 2 fps now.
        if ( context.SimulateBadFps )
        {
            device->sleep(500); // 2 fps
        }
        else
        {
            device->sleep(10);
        }
    }

    device->drop();

    return 0;
}