Continuando nossa série de artigos traduzido do site lazyfoo, agora iremos ver que, da mesma forma que no caso dos eventos de pressionamento de teclas, o SDL possui eventos para lidar com eventos do mouse como movimento, e pressionamento de botões. Nesse artigo iremos criar alguns botões para podermos interagir com eles.
//Button constants const int BUTTON_WIDTH = 300; const int BUTTON_HEIGHT = 200; const int TOTAL_BUTTONS = 4; enum LButtonSprite { BUTTON_SPRITE_MOUSE_OUT = 0, BUTTON_SPRITE_MOUSE_OVER_MOTION = 1, BUTTON_SPRITE_MOUSE_DOWN = 2, BUTTON_SPRITE_MOUSE_UP = 3, BUTTON_SPRITE_TOTAL = 4 };
Para esse artigo, teremos 4 botões na tela. Dependendo do mouse passar sobre um botão, um botão ser pressionado ou liberado ou o mouse ser movido para fora da área do botão, exibiremos um clip diferente. Essas constantes estão aqui para definir tudo isso.
//Texture wrapper class class LTexture { public: //Initializes variables LTexture(); //Deallocates memory ~LTexture(); //Loads image at specified path bool loadFromFile( std::string path ); #ifdef _SDL_TTF_H //Creates image from font string bool loadFromRenderedText( std::string textureText, SDL_Color textColor ); #endif //Deallocates texture void free(); //Set color modulation void setColor( Uint8 red, Uint8 green, Uint8 blue ); //Set blending void setBlendMode( SDL_BlendMode blending ); //Set alpha modulation void setAlpha( Uint8 alpha ); //Renders texture at given point void render( int x, int y, SDL_Rect* clip = NULL, double angle = 0.0, SDL_Point* center = NULL, SDL_RendererFlip flip = SDL_FLIP_NONE ); //Gets image dimensions int getWidth(); int getHeight(); private: //The actual hardware texture SDL_Texture* mTexture; //Image dimensions int mWidth; int mHeight; };
Iremos fazer um pequena modificação na classe da textura. Para esse artigo, não usaremos SDL_ttf para renderizar texto. Isso significa que não precisaremos da função loadFromRenderedText. Ao invés de remover o código que podemos precisar no futuro, iremos envolve-lo com sentencas if def de forma que o compilador irá ignora-lo se não incluirmos SDL_ttf. Como no caso do #include, #ifdef é uma macro que é usada para conversar com o compilador. Nesse caso informa que se SDL_ttf não estiver definida, ignorar essa parte do código.
//The mouse button class LButton { public: //Initializes internal variables LButton(); //Sets top left position void setPosition( int x, int y ); //Handles mouse event void handleEvent( SDL_Event* e ); //Shows button sprite void render(); private: //Top left position SDL_Point mPosition; //Currently used global sprite LButtonSprite mCurrentSprite; };
Aqui temos uma classe que representa um botão. Possui um construtor para inicializa-lo, um método setter para a posição, um manipulador de eventos para o loop de eventos e uma função de renderizarão. Também possui um posição e uma enumeração que diz quais clips renderizar desse botão.
#ifdef _SDL_TTF_H bool LTexture::loadFromRenderedText( std::string textureText, SDL_Color textColor ) { //Get rid of preexisting texture free(); //Render text surface SDL_Surface* textSurface = TTF_RenderText_Solid( gFont, textureText.c_str(), textColor ); if( textSurface == NULL ) { printf( "Unable to render text surface! SDL_ttf Error: %s\n", TTF_GetError() ); } else { //Create texture from surface pixels mTexture = SDL_CreateTextureFromSurface( gRenderer, textSurface ); if( mTexture == NULL ) { printf( "Unable to create texture from rendered text! SDL Error: %s\n", SDL_GetError() ); } else { //Get image dimensions mWidth = textSurface->w; mHeight = textSurface->h; } //Get rid of old surface SDL_FreeSurface( textSurface ); } //Return success return mTexture != NULL; } #endif
LButton::LButton() { mPosition.x = 0; mPosition.y = 0; mCurrentSprite = BUTTON_SPRITE_MOUSE_OUT; } void LButton::setPosition( int x, int y ) { mPosition.x = x; mPosition.y = y; }
void LButton::handleEvent( SDL_Event* e ) { //If mouse event happened if( e->type == SDL_MOUSEMOTION || e->type == SDL_MOUSEBUTTONDOWN || e->type == SDL_MOUSEBUTTONUP ) { //Get mouse position int x, y; SDL_GetMouseState( &x, &y );
Primeiro verificamos se o evento é um evento de movimento (quando o mouse se move), um button down (quando você clica em um botão) ou button up (quando você libera o botão após clicar nele).
Se um desse eventos ocorrer, verificamos a posição do mouse usando SDL_GetMouseState. Dependendo do mouse estar sobre um botão ou não, iremos exibir diferentes clips.
//Check if mouse is in button bool inside = true; //Mouse is left of the button if( x < mPosition.x ) { inside = false; } //Mouse is right of the button else if( x > mPosition.x + BUTTON_WIDTH ) { inside = false; } //Mouse above the button else if( y < mPosition.y ) { inside = false; } //Mouse below the button else if( y > mPosition.y + BUTTON_HEIGHT ) { inside = false; }
Isso é o que esse pedaço do código faz. Se a posição do mouse estiver fora do botão, marca o marcado inside como false. Caso contrário, marca o valor como true.
//Mouse is outside button if( !inside ) { mCurrentSprite = BUTTON_SPRITE_MOUSE_OUT; } //Mouse is inside button else { //Set mouse over sprite switch( e->type ) { case SDL_MOUSEMOTION: mCurrentSprite = BUTTON_SPRITE_MOUSE_OVER_MOTION; break; case SDL_MOUSEBUTTONDOWN: mCurrentSprite = BUTTON_SPRITE_MOUSE_DOWN; break; case SDL_MOUSEBUTTONUP: mCurrentSprite = BUTTON_SPRITE_MOUSE_UP; break; } } } }
Se o ponteiro do mouse não estiver dentro do botão, configuramos o clip mouse out. Se o ponteiro do mouse estiver dentro do botão configuramos o clip mouse over para um movimento do mouse, mouse down para o caso de pressionarmos um botão (click) e mouse up para o caso de soltarmos o botão (pós-click).
void LButton::render() { //Show current button sprite gButtonSpriteSheetTexture.render( mPosition.x, mPosition.y, &gSpriteClips[ mCurrentSprite ] ); }
//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 button events for( int i = 0; i < TOTAL_BUTTONS; ++i ) { gButtons[ i ].handleEvent( &e ); } } //Clear screen SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF ); SDL_RenderClear( gRenderer ); //Render buttons for( int i = 0; i < TOTAL_BUTTONS; ++i ) { gButtons[ i ].render(); } //Update screen SDL_RenderPresent( gRenderer ); }
Existem também eventos para rolagem do mouse que não cobrimos aqui, mas se você der uma olhada na documentação e brincar com ela um pouco não deve ser muito difícil de entende-la, dado o que você já sabe dos demais eventos.