Continuando nossa série de artigos traduzidos do site lazyfoo.net, veremos agora como manipular múltiplas janelas de uma vez com o SDL.
Um dos novos recursos do SDL 2 é ser capaz de gerenciar múltiplas janelas ao mesmo tempo. Nesse artigo, iremos ver como manipular 3 janelas redimensionáveis ao mesmo tempo.
//Total windows const int TOTAL_WINDOWS = 3; 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; //Window dimensions int mWidth; int mHeight; //Window focus bool mMouseFocus; bool mKeyboardFocus; bool mFullScreen; bool mMinimized; bool mShown; };
Aqui temos nosso empacotador da janela que usamos no artigo anterior com alguns ajustes. Queremos ser capazes de capturar o foco e dizer se a janela está sendo exibida, então adicionamos funções para fazer isso.
Cada janela irá ter seu próprio renderizador, assim adicionamos um atributo para guardar essa informação. Também iremos manter um registro do ID da janela para que possamos dizer qual eventos pertencem a qual janela e também adicionamos uma flag para manter um registro de qual janela está sendo exibida.
//Our custom windows LWindow gWindows[ TOTAL_WINDOWS ];
No exemplo desse artigo, teremos três janelas alocadas globalmente.
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 identifier mWindowID = SDL_GetWindowID( mWindow ); //Flag as opened mShown = true; } } else { printf( "Window could not be created! SDL Error: %s\n", SDL_GetError() ); } return mWindow != NULL && mRenderer != NULL; }
Aqui temos o código para criação da janela e do renderizado. É basicamente o mesmo código que temos usado desde sempre, só que agora ele é executada de dentro da classe que empacota a janela. Temos que nos certificar de capturar o ID da janela depois de cria-la, pois iremos precisar desse ID para a manipulação dos eventos.
void LWindow::handleEvent( SDL_Event& e ) { //If an event was detected for this window if( e.type == SDL_WINDOWEVENT && e.window.windowID == mWindowID ) { //Caption update flag bool updateCaption = false; All events from all windows go onto the same event queue, so to know which events belong to which window we check that the event's window ID matches ours. switch( e.window.event ) { //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;
Quando você tem múltiplas janelas, Clicar no botão com o X da janela não significa necessariamente que estamos encarrando o programa. O que faremos ao invés disso é ocultar cada janela quando clicarmos nesse botão. Dessa forma, temos que manter um registro de quando a janela é ocultada/exibida checando pelos eventos SDL_WINDOWEVENT_SHOWN/SDL_WINDOWEVENT_HIDDEN.
//Hide on close case SDL_WINDOWEVENT_CLOSE: SDL_HideWindow( mWindow ); break; } //Update window caption with new data if( updateCaption ) { std::stringstream caption; caption << "SDL Tutorial - ID: " << mWindowID << " MouseFocus:" << ( ( mMouseFocus ) ? "On" : "Off" ) << " KeyboardFocus:" << ( ( mKeyboardFocus ) ? "On" : "Off" ); SDL_SetWindowTitle( mWindow, caption.str().c_str() ); } } }
Quando você temo múltiplas janelas, clicar no botão com o X é interpretado como um evento SDL_WINDOWEVENT_CLOSE. Quando temos um desses eventos iremos ocultar a janela usando SDL_HideWindow.
void LWindow::focus() { //Restore window if needed if( !mShown ) { SDL_ShowWindow( mWindow ); } //Move window forward SDL_RaiseWindow( mWindow ); }
Aqui nossa função está capturando o foco de uma janela. Primeiro, checamos se a janela está sendo exibida e em seguida exibimos ela co m SDL_ShowWindow caso não esteja. Depois, chamamos SDL_RaiseWindow para focar nessa janela.
void LWindow::render() { if( !mMinimized ) { //Clear screen SDL_SetRenderDrawColor( mRenderer, 0xFF, 0xFF, 0xFF, 0xFF ); SDL_RenderClear( mRenderer ); //Update screen SDL_RenderPresent( mRenderer ); } }
Como antes, apenas desejamos renderizar se a janela não estiver minimizada.
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!" ); } //Create window if( !gWindows[ 0 ].init() ) { printf( "Window 0 could not be created!\n" ); success = false; } } return success; }
Na função de inicialização, abrimos uma única janela para verificar se a criação da janela está funcionando de forma apropriada.
void close() { //Destroy windows for( int i = 0; i < TOTAL_WINDOWS; ++i ) { gWindows[ i ].free(); } //Quit SDL subsystems SDL_Quit(); }
Na função de limpeza, fechamos qualquer janela que esteja aberta.
//Initialize the rest of the windows for( int i = 1; i < TOTAL_WINDOWS; ++i ) { gWindows[ i ].init(); } //Main loop flag bool quit = false; //Event handler SDL_Event e;
Antes de entramos no loop principal, abrimos o restante das janelas que temos.
//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 for( int i = 0; i < TOTAL_WINDOWS; ++i ) { gWindows[ i ].handleEvent( e ); } //Pull up window if( e.type == SDL_KEYDOWN ) { switch( e.key.keysym.sym ) { case SDLK_1: gWindows[ 0 ].focus(); break; case SDLK_2: gWindows[ 1 ].focus(); break; case SDLK_3: gWindows[ 2 ].focus(); break; } } }
No loop principal, depois de termos lidado com os eventos de todas as janelas, lidamos com alguns pressionamentos de teclas especiais. nesse exemplo, quando pressionamos 1, 2 ou 3, damos foco na janela correspondente.
//Update all windows for( int i = 0; i < TOTAL_WINDOWS; ++i ) { gWindows[ i ].render(); } //Check all windows bool allWindowsClosed = true; for( int i = 0; i < TOTAL_WINDOWS; ++i ) { if( gWindows[ i ].isShown() ) { allWindowsClosed = false; break; } } //Application closed all windows if( allWindowsClosed ) { quit = true; } }
Em seguida, renderizamos todas as janelas e verificamos cada janela para ver se elas estão sendo exibidas. Se todas elas estiverem fechadas, ajustamos a flag de saída para encerrar o programa.
Para encerrar, cabe salientar que esse exemplo de fato não renderizar nenhuma imagem dentro das janelas. Isso envolveria ter que gerenciais os renderizadores e as janelas e fazer com que elas compartilhem recursos. Não há uma maneira certa de fazer isso, e a melhor maneira depende inteiramente do tipo de aplicação que você estiver construindo. Eu recomendo a leitura da documentação do SDL para entender como os renderizados funcionam e então experimentar para descobrir a melhor forma para você gerenciar seus recursos.
Baixe os arquivos de mídia e do código fontes do exemplo desse artigo aqui