Tutorial de SDL – Parte 17 – Eventos do mouse

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
Para certificar que nossa código seja compilador sem SDL_ttf, novamente nós envolvemos a função de carregamento da fonte em um condição ifdef.
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;
}
Aqui temos o construtor para o botão e a função de definição da posição. Como você pode ver, elas inicializam o clip padrão e ajustam a posição.
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 );
Aqui temos o objetivo desse artigo, onde lidamos com os eventos do mouse. Essa função será chamada pelo loop de eventos e irar lidar com um evento da fila de eventos por vez para botões individuais.
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;
 }
Aqui queremos verificar se o mouse está dentro de um botão ou não. Como usamos um sistema de coordenadas diferente no SDL, o ponto de origem do botão está no canto superior esquerdo. Isso significa que cada coordenada x menor do que a posição x é externa ao botão e cada coordenada y menor do que a posição y também. Tudo que estiver à direita do botão é a posição x + a largura e tudo que estiver abaixo do botão é a posição y + a altura.
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;
 }
 }
 }
}
Finalmente, configuramos o clip do botão dependendo do ponteiro do mouse estar dentro do botão e do evento do mouse.
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 ] );
}
Na função de renderização, apenas renderizamos o clip atual do botão na posição dele.
 //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 );
 }
Aqui temos o loop principal. No loop de eventos, lidamos com o evento de saída e os eventos para todos os botões. Na seção de renderização, todos os botões são renderizados na tela.
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.
Baixe os arquivos de mídia e o código fonte desse artigo aqui.