Tutorial de SDL – Parte 35 – Eventos da janela

Continuando nossa série de artigos traduzidos do site lazyfoo.net, agora veremos tratar eventos da janela com SDL.

O SDL suporta janelas redimensionáveis. Quando você tem esse tipo de janela, existem alguns eventos adicionais que precisam ser manipulados, que é o que iremos fazer aqui.

class LWindow
{
    public:
        //Intializes internals
        LWindow();

        //Creates window
        bool init();

        //Creates renderer from internal window
        SDL_Renderer* createRenderer();

        //Handles window events
        void handleEvent( SDL_Event& e );

        //Deallocates internals
        void free();

        //Window dimensions
        int getWidth();
        int getHeight();

        //Window focii
        bool hasMouseFocus();
        bool hasKeyboardFocus();
        bool isMinimized();

    private:
        //Window data
        SDL_Window* mWindow;

        //Window dimensions
        int mWidth;
        int mHeight;

        //Window focus
        bool mMouseFocus;
        bool mKeyboardFocus;
        bool mFullScreen;
        bool mMinimized;
};

Aqui temos nossa classe para a janela que usaremos para empacotar SDL_Window. Possui um construtor, um inicializar para criar a janela, uma função para criar um renderizador a partir da janela, um manipulador de eventos, um desalocador e algumas funções acessórias para ler diversos atributos da janela.

Em termos de atributos, temos a janela que estamos empacotando, as dimensões da janela, e flags para tipos os tipos de foco que a janela possui. Iremos dar mais detalhes disso mais adiante.

//Our custom window
LWindow gWindow;

//The window renderer
SDL_Renderer* gRenderer = NULL;

//Scene textures
LTexture gSceneTexture;
We'll be using our window as a global object.
LWindow::LWindow()
{
    //Initialize non-existant window
    mWindow = NULL;
    mMouseFocus = false;
    mKeyboardFocus = false;
    mFullScreen = false;
    mMinimized = false;
    mWidth = 0;
    mHeight = 0;
}

No construtor, inicializamos nossas variáveis.

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;
    }

    return mWindow != NULL;
}

Nossa função de inicialização cria a janela com a flag SDL_WINDOW_RESIZABLE que permite que nossa janela seja redimensionável. Se a função tiver sucesso na criação da janela, ajustamos as flags correspondentes e as dimensões. Em seguida retornamos se a janela é null ou não.

SDL_Renderer* LWindow::createRenderer()
{
    return SDL_CreateRenderer( mWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC );
}

Aqui lidamos com a criação do renderizador da janela. Retornamos o renderizador criado pois a renderização será feita fora dessa classe.

void LWindow::handleEvent( SDL_Event& e )
{
    //Window event occured
    if( e.type == SDL_WINDOWEVENT )
    {
        //Caption update flag
        bool updateCaption = false;

Em nosso manipulador de eventos, estaremos verificando por eventos do tipo SDL_WINDOWEVENT. SDL_WindowEvents são na verdade uma família de eventos. Dependendo do evento podemos ter que atualizar o titulo da janela, por isso temos uma flag para manter um registro disso.

        switch( e.window.event )
        {
            //Get new dimensions and repaint on window size change
            case SDL_WINDOWEVENT_SIZE_CHANGED:
            mWidth = e.window.data1;
            mHeight = e.window.data2;
            SDL_RenderPresent( gRenderer );
            break;

            //Repaint on exposure
            case SDL_WINDOWEVENT_EXPOSED:
            SDL_RenderPresent( gRenderer );
            break;

Quando temos um evento da janela queremos checar SDL_WindowEventID para vermos que timos de evento ele é. Um SDL_WINDOWEVENT_SIZE_CHANGED é um evento de redimensionamento, então nós lemos as novas dimensões e atualizamos a imagem da tela.

Um evento SDL_WINDOWEVENT_EXPOSED significa que a imagem foi escurecida de alguma forma, e agora não está mais escurecida, assim temos que redesenhar a janela.

            //Mouse entered window
            case SDL_WINDOWEVENT_ENTER:
            mMouseFocus = true;
            updateCaption = true;
            break;
            
            //Mouse left window
            case SDL_WINDOWEVENT_LEAVE:
            mMouseFocus = false;
            updateCaption = true;
            break;

            //Window has keyboard focus
            case SDL_WINDOWEVENT_FOCUS_GAINED:
            mKeyboardFocus = true;
            updateCaption = true;
            break;

            //Window lost keyboard focus
            case SDL_WINDOWEVENT_FOCUS_LOST:
            mKeyboardFocus = false;
            updateCaption = true;
            break;

SDL_WINDOWEVENT_ENTER/SDL_WINDOWEVENT_LEAVE lida os eventos relacionados ao ponteiro do mouse entrando ou saindo da tela. SDL_WINDOWEVENT_FOCUS_GAINED/SDL_WINDOWEVENT_FOCUS_LOST tem a ver  com a janela recebendo entrada do teclado. Como nosso titulo mantém registro do foco do teclado/mouse, ajustamos a flag de atualização do titulo quando um desses eventos ocorre.

            //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;
        }

Finalmente, lidamos aqui com o evento de minimização, maximização ou restauração da minimização da janela.

        //Update window caption with new data
        if( updateCaption )
        {
            std::stringstream caption;
            caption << "SDL Tutorial - MouseFocus:" << ( ( mMouseFocus ) ? "On" : "Off" ) << " KeyboardFocus:" << ( ( mKeyboardFocus ) ? "On" : "Off" );
            SDL_SetWindowTitle( mWindow, caption.str().c_str() );
        }
    }

Se o título precisa ser atualizado, carregamos uma string com os dados atualizados e atualizados o título com SDL_SetWindowTitle.

    //Enter exit full screen on return key
    else if( e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RETURN )
    {
        if( mFullScreen )
        {
            SDL_SetWindowFullscreen( mWindow, SDL_FALSE );
            mFullScreen = false;
        }
        else
        {
            SDL_SetWindowFullscreen( mWindow, SDL_TRUE );
            mFullScreen = true;
            mMinimized = false;
        }
    }
}

Nesse exemplo, estaremos alternando para o modo de tela cheia com a tecla enter. Podemos passar para o modo de tela cheia usando SDL_SetWindowFullscreen.

int LWindow::getWidth()
{
    return mWidth;
}

int LWindow::getHeight()
{
    return mHeight;
}

bool LWindow::hasMouseFocus()
{
    return mMouseFocus;
}

bool LWindow::hasKeyboardFocus()
{
    return mKeyboardFocus;
}

bool LWindow::isMinimized()
{
    return mMinimized;
}

Aqui temos uma repassada rápida das funções acessórias que estamos usando.

        //Create window
        if( !gWindow.init() )
        {
            printf( "Window could not be created! SDL Error: %s\n", SDL_GetError() );
            success = false;
        }
        else
        {
            //Create renderer for window
            gRenderer = gWindow.createRenderer();
            if( gRenderer == NULL )
            {
                printf( "Renderer could not be created! SDL Error: %s\n", SDL_GetError() );
                success = false;
            }

Na nossa função de inicialização criamos nossa janela e renderizador apenas dessa vez com nosso empacotador da janela.

void close()
{
    //Free loaded images
    gSceneTexture.free();

    //Destroy window    
    SDL_DestroyRenderer( gRenderer );
    gWindow.free();

    //Quit SDL subsystems
    IMG_Quit();
    SDL_Quit();
}

Na nossa função de limpeza desalocamos nossa janela e renderizador.

            //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 );
                }

                //Only draw when not minimized
                if( !gWindow.isMinimized() )
                {
                    //Clear screen
                    SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
                    SDL_RenderClear( gRenderer );

                    //Render text textures
                    gSceneTexture.render( ( gWindow.getWidth() - gSceneTexture.getWidth() ) / 2, ( gWindow.getHeight() - gSceneTexture.getHeight() ) / 2 );

                    //Update screen
                    SDL_RenderPresent( gRenderer );
                }
            }

No loop principal, garantimos de que passamos os eventos para o empacotador da janela para que ele lide com os eventos de redimensionamento e na parte da renderização do nosso código garantimos de que apenas renderizar quando a janela não está minimizada pois isso poderia causar alguns bugs (tentar renderizar uma janela minimizada).

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