Tutorial de SDL – Parte 37 – Múltiplas telas

Continuando nossa série de artigos traduzidos do site lazyfoo, agora veremos como o SDL lida com múltiplas telas.
Um recurso novo do SDL 2 é a habilidade de lidar com múltiplas telas. Aqui iremos fazer nossa janela pular de tela em tela.

class LWindow
{
    public:
        //Intializes internals
        LWindow();
        //Creates window
        bool init();
        //Handles window events
        void handleEvent( SDL_Event& e );
        //Focuses on window
        void focus();
        //Shows windows contents
        void render();
        //Deallocates internals
        void free();
        //Window dimensions
        int getWidth();
        int getHeight();
        //Window focii
        bool hasMouseFocus();
        bool hasKeyboardFocus();
        bool isMinimized();
        bool isShown();
    private:
        //Window data
        SDL_Window* mWindow;
        SDL_Renderer* mRenderer;
        int mWindowID;
        int mWindowDisplayID;
        //Window dimensions
        int mWidth;
        int mHeight;
        //Window focus
        bool mMouseFocus;
        bool mKeyboardFocus;
        bool mFullScreen;
        bool mMinimized;
        bool mShown;
};

Aqui temos a nova janela dos artigos anteriores desta série com um ID associado para que possamos saber em qual tela a janela está.

//Our custom window
LWindow gWindow;
//Display data
int gTotalDisplays = 0;
SDL_Rect* gDisplayBounds = NULL

Todas as nossas telas  possuem um ID inteiro e um retângulo associado à elas de forma que possamos saber a posição e dimensão de cada tela de nossa área de trabalho.

bool LWindow::init()
{
    //Create window
    mWindow = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE );
    if( mWindow != NULL )
    {
        mMouseFocus = true;
        mKeyboardFocus = true;
        mWidth = SCREEN_WIDTH;
        mHeight = SCREEN_HEIGHT;
        //Create renderer for window
        mRenderer = SDL_CreateRenderer( mWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC );
        if( mRenderer == NULL )
        {
            printf( "Renderer could not be created! SDL Error: %s\n", SDL_GetError() );
            SDL_DestroyWindow( mWindow );
            mWindow = NULL;
        }
        else
        {
            //Initialize renderer color
            SDL_SetRenderDrawColor( mRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
            //Grab window identifiers
            mWindowID = SDL_GetWindowID( mWindow );
            mWindowDisplayID = SDL_GetWindowDisplayIndex( mWindow );
            //Flag as opened
            mShown = true;
        }
    }
    else
    {
        printf( "Window could not be created! SDL Error: %s\n", SDL_GetError() );
    }
    return mWindow != NULL && mRenderer != NULL;
}

Nosso código de criação de janela é basicamente o mesmo do artigo anterior, mas agora fazemos uma chamada para SDL_GetWindowDisplayIndex para que possamos saber em qual tela a janela foi criada.

void LWindow::handleEvent( SDL_Event& e )
{
    //Caption update flag
    bool updateCaption = false;
    //If an event was detected for this window
    if( e.type == SDL_WINDOWEVENT && e.window.windowID == mWindowID )
    {
        switch( e.window.event )
        {
            //Window moved
            case SDL_WINDOWEVENT_MOVED:
            mWindowDisplayID = SDL_GetWindowDisplayIndex( mWindow );
            updateCaption = true;
            break;
            //Window appeared
            case SDL_WINDOWEVENT_SHOWN:
            mShown = true;
            break;
            //Window disappeared
            case SDL_WINDOWEVENT_HIDDEN:
            mShown = false;
            break;
            //Get new dimensions and repaint
            case SDL_WINDOWEVENT_SIZE_CHANGED:
            mWidth = e.window.data1;
            mHeight = e.window.data2;
            SDL_RenderPresent( mRenderer );
            break;
            //Repaint on expose
            case SDL_WINDOWEVENT_EXPOSED:
            SDL_RenderPresent( mRenderer );
            break;
            //Mouse enter
            case SDL_WINDOWEVENT_ENTER:
            mMouseFocus = true;
            updateCaption = true;
            break;
            //Mouse exit
            case SDL_WINDOWEVENT_LEAVE:
            mMouseFocus = false;
            updateCaption = true;
            break;
            //Keyboard focus gained
            case SDL_WINDOWEVENT_FOCUS_GAINED:
            mKeyboardFocus = true;
            updateCaption = true;
            break;
            //Keyboard focus lost
            case SDL_WINDOWEVENT_FOCUS_LOST:
            mKeyboardFocus = false;
            updateCaption = true;
            break;
            //Window minimized
            case SDL_WINDOWEVENT_MINIMIZED:
            mMinimized = true;
            break;
            //Window maxized
            case SDL_WINDOWEVENT_MAXIMIZED:
            mMinimized = false;
            break;
            //Window restored
            case SDL_WINDOWEVENT_RESTORED:
            mMinimized = false;
            break;
            //Hide on close
            case SDL_WINDOWEVENT_CLOSE:
            SDL_HideWindow( mWindow );
            break;
        }
    }

Aqui no manipulador de eventos de nossa janela lidaremos com o evento SDL_WINDOWEVENT_MOVED de forma que possamos atualizar a tela que a janela está usando usando SDL_GetWindowDisplayIndex.

    else if( e.type == SDL_KEYDOWN )
    {
        //Display change flag
        bool switchDisplay = false;
        //Cycle through displays on up/down
        switch( e.key.keysym.sym )
        {
            case SDLK_UP:
            ++mWindowDisplayID;
            switchDisplay = true;
            break;
            case SDLK_DOWN:
            --mWindowDisplayID;
            switchDisplay = true;
            break;
        }

Quando pressionamos para cima ou para baixo mudamos o índice da tela de forma que nos movemos para a próxima tela.

        //Display needs to be updated
        if( switchDisplay )
        {
            //Bound display index
            if( mWindowDisplayID < 0 ) { mWindowDisplayID = gTotalDisplays - 1; } else if( mWindowDisplayID >= gTotalDisplays )
            {
                mWindowDisplayID = 0;
            }
            //Move window to center of next display
            SDL_SetWindowPosition( mWindow, gDisplayBounds[ mWindowDisplayID ].x + ( gDisplayBounds[ mWindowDisplayID ].w - mWidth ) / 2, gDisplayBounds[ mWindowDisplayID ].y + ( gDisplayBounds[ mWindowDisplayID ].h - mHeight ) / 2 );
            updateCaption = true;
        }
    }
    //Update window caption with new data
    if( updateCaption )
    {
        std::stringstream caption;
        caption << "SDL Tutorial - ID: " << mWindowID << " Display: " << mWindowDisplayID << " MouseFocus:" << ( ( mMouseFocus ) ? "On" : "Off" ) << " KeyboardFocus:" << ( ( mKeyboardFocus ) ? "On" : "Off" );
        SDL_SetWindowTitle( mWindow, caption.str().c_str() );
    }
}

Se precisarmos mover para a próxima tela, primeiro nos certificamos de que a tela tem um índice válido. Então atualizamos a posição da janela com SDL_SetWindowPosition. Essa chamada irã centralizar a janela na próxima tela.

bool init()
{
    //Initialization flag
    bool success = true;
    //Initialize SDL
    if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
        printf( "SDL could not initialize! SDL Error: %s\n", SDL_GetError() );
        success = false;
    }
    else
    {
        //Set texture filtering to linear
        if( !SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "1" ) )
        {
            printf( "Warning: Linear texture filtering not enabled!" );
        }
        //Get number of displays
        gTotalDisplays = SDL_GetNumVideoDisplays();
        if( gTotalDisplays < 2 )
        {
            printf( "Warning: Only one display connected!" );
        }

Na nossa função de inicialização descobrimos quantas telas estão conectadas ao computador usando SDL_GetNumVideoDisplays. Se houver apenas 1 tela conectada, será exibido um aviso.

        //Get bounds of each display
        gDisplayBounds = new SDL_Rect[ gTotalDisplays ];
        for( int i = 0; i < gTotalDisplays; ++i )
        {
            SDL_GetDisplayBounds( i, &gDisplayBounds[ i ] );
        }
        //Create window
        if( !gWindow.init() )
        {
            printf( "Window could not be created!\n" );
            success = false;
        }
    }
    return success;
}

Agora que sabemos quantas telas estão conectadas, alocaremos retângulos para cada uma delas e obtemos os limites de cada uma delas usando SDL_GetDisplayBounds. Depois disso, inicializamos nossa janela.

        //Main loop flag
        bool quit = false;
        //Event handler
        SDL_Event e;
        //While application is running
        while( !quit )
        {
            //Handle events on queue
            while( SDL_PollEvent( &e ) != 0 )
            {
                //User requests quit
                if( e.type == SDL_QUIT )
                {
                    quit = true;
                }
                //Handle window events
                gWindow.handleEvent( e );
            }
            //Update window
            gWindow.render();
        }

Como nosso código está bem encapsulado, o loop principal não precisa ser alterado já que todas as alterações foram feitas sob o capô.

Baixe os arquivos de mídia e de código fonte desse artigo aqui.