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.