Buttons:
Similar to keys on a keyboard, SDL will tell us when a mouse button is pressed or released. To determine which button is pressed, the SDL_Event structure has a data field called 'button' which also has its own field called 'button'. The sub-field button is of the type Uint8 (or unsigned char) and can be compared against several #define statements which create an enumeration.
The term SDL_BUTTON_LEFT is defined as 1, SDL_BUTTON_MIDDLE is defined as 2, and SDL_BUTTON_RIGHT is defined as 3. When any of these three buttons are pressed (middle is triggered by pressing in the scroll wheel), an SDL_MOUSEBUTTONDOWN event type occurs. Similarly, if one of these buttons is released, an SDL_MOUSEBUTTONUP event is processed. We can handle these button presses with variables like we did for keyboard keys.
Here is an example which also displays three different colors for each button press:
For this example, I held the left mouse button first, but also held down the middle button at the same time which caused alternating between drawing magenta and yellow rectangles. Since both button variables are true for those frames, both draw commands are done in these cycles. Note that I did not alternate between the buttons quickly, but this would would produce similar output (and require very precise button pressing). I finally press the right button and hold the left to produce a magenta colored screen.
The right_button variable should track initial presses while left_button and middle_button track button holding. The right_button variable is not set to 0 during an SDL_MOUSEBUTTONUP event type, but doing so would not affect its ability to detect initial presses. This indifference worked well in the keyboard array examples from the previous post.
Scrolling:
SDL identifies scrolling up and scrolling down as button presses. However, these buttons cannot be held; the moment either scroll triggers an SDL_MOUSEBUTTONDOWN event, it is followed right after by its SDL_MOUSEBUTTONUP event in the same frame! If we attempt to track holding of scroll buttons like that of other buttons, it will simply be 0 outside of the inner while loop.
Since it is extremely difficult to hold a scroll up or scroll down in place, we won't try to track it like a held button. To detect presses, we need two int variables which will be set to 0 every cycle start and to 1 if SDL_MOUSEBUTTONDOWN occurs for scroll buttons. There are additional #define statement enumerations for the scroll buttons: SDL_BUTTON_WHEELUP is 4 and SDL_BUTTON_WHEELDOWN is 5. There exist two additional enumerations SDL_BUTTON_X1 (6) and SDL_BUTTON_X2 (7), but I cannot seem to find documentation about them.
The following example should change the screen color based on scrolling:
/*thanks to tohtml.com for syntax highlighting*/ #include <SDL/SDL.h> #include <stdio.h> SDL_Surface *screen; /*visible surface*/ SDL_Event input; /*holds input*/ int loop = 1; /*set to zero to exit*/ Uint8 pressed; /*integers to track mouse scroll states*/ int scroll_up = 0, scroll_down = 0; Uint32 gray; /*changing color of screen*/ Uint8 light = 0; /*level of brightness in gray color*/ int main(int argc, char** argv) { SDL_Rect block; /*Rectangle to cover screen*/ block.x = 0; block.y = 0; block.w = 640; block.h = 480; /*start up SDL and setup window*/ SDL_Init(SDL_INIT_EVERYTHING); screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE); /*loop is needed to keep window open*/ while (loop != 0) { /*make scrolls a one-press detection*/ scroll_up = 0; scroll_down = 0; /*read all events per cycle*/ while (SDL_PollEvent(&input)) { if (input.type == SDL_QUIT) loop = 0; /*check for mouse button presses*/ if (input.type == SDL_MOUSEBUTTONDOWN) { pressed = input.button.button; /*check which button is pressed*/ if (pressed == SDL_BUTTON_WHEELUP) { scroll_up = 1; } if (pressed == SDL_BUTTON_WHEELDOWN) { scroll_down = 1; } } } /*change brightness of gray if scroll detected*/ if (scroll_up == 1) { light += 16; printf("brightness increased: %u\n", light); } if (scroll_down == 1) { light -= 16; printf("brightness decreased: %u\n", light); } /*set red, green, and blue to brightness value*/ gray = SDL_MapRGB(screen->format, light, light, light); SDL_FillRect(screen, &block, gray); SDL_Delay(20); /*wait 20ms*/ SDL_Flip(screen); /*update screen*/ } /*perform final commands then exit*/ SDL_Quit(); /*shutdown SDL*/ fflush(stdout); /*update stdout*/ return 0; } | ||
prompt/stdout: brightness increased: 16 brightness increased: 32 brightness increased: 48 brightness increased: 64 brightness increased: 80 brightness increased: 96 brightness decreased: 80 brightness decreased: 64 brightness decreased: 48 | ||
output inside window:
|
I scroll upward six times and downward three which results in a brightness of 48 (6*16 - 3*16). Were I to scroll downward at 0 brightness, the brightness level would jump to 240 (and jump to 0 if I scroll back up). This is a side effect of C arithmetic: for unsigned chars, 240 + 16 == 0. For this example, I recommend trying for different output.
The rectangle here is drawn every frame; button presses only affect the color of the drawn rectangle. This color is determined by one variable, called 'light', which is used as the red, green, and blue components of the color. If the red value, green value, and blue value are all equal, the result is a gray color (or white or black).
Unlike other buttons, the wheel or scroll button variables should NOT be set to 0 during an SDL_MOUSEBUTTONUP event type since this will cancel the 1 set during the SDL_MOUSEBUTTONDOWN event. We must still set these variables to 0 each frame in order to only account for initial presses.
Mouse Motion:
The last aspect of mouse input is motion or cursor position. Rather than using the "button" field of the SDL_Event structure, the "motion" field is what tracks mouse movements. The "motion" field has sub-fields "x" and "y" which are of type Uint16. They are the cursor's x and y positions within the window (the origin is the upper-left corner).
Some other sub-fields of the "motion" field are "xrel" and "yrel". Unlike "x" and "y", they are signed values (Sint16) to allow for negative numbers. These rel sub-fields represent the change in position from the last SDL_MOUSEMOTION event type (xrel == x_new - x_old). To represent change per frame, we can use our own signed short variables which are assigned 0 every cycle and assigned the rel sub-fields during an SDL_MOUSEMOTION event.
The following example will draw a rectangle at the mouse's current position:
/*thanks to tohtml.com for syntax highlighting*/ #include <SDL/SDL.h> #include <stdio.h> SDL_Surface *screen; /*visible surface*/ SDL_Event input; /*holds input*/ int loop = 1; /*set to zero to exit*/ /*integers to track mouse position and motion*/ int mouse_x = 0, mouse_y = 0; int mouse_xrel = 0, mouse_yrel = 0; /*Rectangle drawn at mouse position*/ SDL_Rect mouse_rect; int main(int argc, char** argv) { SDL_Rect backdrop; /*Rectangle to cover screen*/ backdrop.x = 0; backdrop.y = 0; backdrop.w = 640; backdrop.h = 480; /*start up SDL and setup window*/ SDL_Init(SDL_INIT_EVERYTHING); screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE); /*loop is needed to keep window open*/ while (loop != 0) { /*to account for frames of no mouse movement, we can set relative positions to zero*/ mouse_xrel = 0; mouse_yrel = 0; /*read all events per cycle*/ while (SDL_PollEvent(&input)) { if (input.type == SDL_QUIT) loop = 0; /*check for mouse movement*/ if (input.type == SDL_MOUSEMOTION) { mouse_x = input.motion.x; mouse_y = input.motion.y; mouse_xrel = input.motion.xrel; mouse_yrel = input.motion.yrel; } } /*if mouse has moved since last frame, print coords*/ if (mouse_xrel != 0 || mouse_yrel != 0) { printf("mouse moved to (%u, %u)\n", mouse_x, mouse_y); } /*draw screen-sized, black rectangle*/ SDL_FillRect(screen, &backdrop, 0x000000); /*set dimensions of rectangle drawn near cursor*/ mouse_rect.w = 50; mouse_rect.h = 50; /*center the rectangle on the cursor position*/ mouse_rect.x = mouse_x - mouse_rect.w / 2; mouse_rect.y = mouse_y - mouse_rect.h / 2; /*draw white rectangle*/ SDL_FillRect(screen, &mouse_rect, 0xffffff); SDL_Delay(20); /*wait 20ms*/ SDL_Flip(screen); /*update screen*/ } /*perform final commands then exit*/ SDL_Quit(); /*shutdown SDL*/ fflush(stdout); /*update stdout*/ return 0; } | ||
prompt/stdout: mouse moved to (428, 6) mouse moved to (440, 19) mouse moved to (450, 27) mouse moved to (459, 31) mouse moved to (470, 37) mouse moved to (484, 44) mouse moved to (495, 47) mouse moved to (503, 48) mouse moved to (539, 43) mouse moved to (566, 31) mouse moved to (582, 19) mouse moved to (593, 7) | ||
output inside window:
|
This example will track all movement of the mouse, so to avoid hundreds of lines of output, I moved the mouse down and to the right and swung upward to the exit button in an arc. Since the program only tracks on-screen mouse positions, the last position is at the top of the visible screen. The rectangle is cut off at the top because it is centered at the last on-screen mouse position (top of screen).
Had the white rectangle been given mouse_x and mouse_y as its x and y, it would follow the mouse with its upper-left corner. This is why we can subtract half the width and height to center the rectangle on the cursor (by shifting it toward the origin). Also, assigning 50 as a width and height was an arbitrary decision; feel free to use whichever values you want.
The screen-size rectangle is still in use for this example: this is to clear the screen. To see what happens by not drawing it, try commenting or removing the line "SDL_FillRect(screen, &backdrop, 0x000000);". Basically, the command "SDL_Flip(screen);" shows an updated screen, but does not clear the previous frame; the result is a trail of white as you move the rectangle around. By drawing a screen-sized, black rectangle each frame, we can overwrite the previous frames.
Built-in, SDL Mouse Management:
Like the keyboard, SDL has a built-in system for managing the mouse. There exist two functions, called SDL_GetMouseState and SDL_GetRelativeMouseState, both of which require two pointers to int variables and return a Uint8 variable. The pointers are for int variables we want to assign x and y to, while the Uint8 is a value which represents a set of flags. Since a button variable is only 0 or 1, each bit of an unsigned char can represent a button flag: left button is the right-most bit, middle is just left of it, right is just left of middle, and so on.
The first function will take the addresses of two int variables and assign to them the position of the mouse in relation to the origin. The second function will assign the relative x and y values (change between frames) to the input addresses. Both return the same Uint8 value if done in the same frame.
In order to determine which buttons are pressed from the set of bits returned, we need bitwise and. We can use powers of two, but SDL provides enumerations called SDL_BUTTON_LMASK, SDL_BUTTON_MMASK, and SDL_BUTTON_RMASK, which are essentially 1, 2, and 4. If we assign the Uint8 output of SDL_GetMouseState to an unsigned char 'button_flags', we can test the middle button with "if ((button_flags & SDL_BUTTON_MMASK) != 0)".
Unfortunately, this approach does not appear to work for scroll buttons and requires creating more variables for one-press detections. For simple cases of only needing three, held buttons and either relative or absolute position, this approach works well, but not for general input processing.
Here is an example which uses absolute cursor position and the three visible mouse buttons:
/*thanks to tohtml.com for syntax highlighting*/ #include <SDL/SDL.h> #include <stdio.h> SDL_Surface *screen; /*visible surface*/ SDL_Event input; /*holds input*/ int loop = 1; /*set to zero to exit*/ /*integers to track mouse position and button states*/ int mouse_x, mouse_y; unsigned char button_flags; /*Rectangle drawn at mouse position*/ SDL_Rect mouse_rect; /*colors for the rectangle*/ Uint32 red, green, blue, rect_color; int main(int argc, char** argv) { SDL_Rect backdrop; /*Rectangle to cover screen*/ backdrop.x = 0; backdrop.y = 0; backdrop.w = 640; backdrop.h = 480; /*start up SDL and setup window*/ SDL_Init(SDL_INIT_EVERYTHING); screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE); /*set colors after setting up screen*/ red = SDL_MapRGB(screen->format, 255, 0, 0); green = SDL_MapRGB(screen->format, 0, 255, 0); blue = SDL_MapRGB(screen->format, 0, 0, 255); /*loop is needed to keep window open*/ while (loop != 0) { /*read all events per cycle*/ while (SDL_PollEvent(&input)) { /*check for exit button clicks*/ if (input.type == SDL_QUIT) loop = 0; } /*get mouse position and button flags*/ button_flags = SDL_GetMouseState(&mouse_x, &mouse_y); /*draw screen-sized, black rectangle*/ /*SDL_FillRect(screen, &backdrop, 0x000000);*/ rect_color = 0xffffffff; /*initially white*/ /*set color based on mouse presses*/ if ((button_flags & SDL_BUTTON_LMASK) != 0) { printf("left button: color set to red\n"); rect_color = red; } if ((button_flags & SDL_BUTTON_MMASK) != 0) { printf("middle button: color set to green\n"); rect_color = green; } if ((button_flags & SDL_BUTTON_RMASK) != 0) { printf("right button: color set to blue\n"); rect_color = blue; } /*set dimensions of rectangle drawn near cursor*/ mouse_rect.w = 50; mouse_rect.h = 50; /*center the rectangle on the cursor position*/ mouse_rect.x = mouse_x - mouse_rect.w / 2; mouse_rect.y = mouse_y - mouse_rect.h / 2; /*draw white rectangle*/ SDL_FillRect(screen, &mouse_rect, rect_color); SDL_Delay(20); /*wait 20ms*/ SDL_Flip(screen); /*update screen*/ } /*perform final commands then exit*/ SDL_Quit(); /*shutdown SDL*/ fflush(stdout); /*update stdout*/ return 0; } |
prompt/stdout: left button: color set to red left button: color set to red left button: color set to red right button: color set to blue right button: color set to blue middle button: color set to green middle button: color set to green middle button: color set to green middle button: color set to green middle button: color set to green |
output inside window: |
This example works like the previous where a rectangle follows the cursor. The color of this rectangle is automatically set to white every frame, but can be changed by holding one of the three visible mouse buttons. The prompt tells which buttons are pressed and what color the rectangle should be. Also, I commented out the screen-clearing command to show a timeline of events in this picture. You are free to remove the comment slashes in front if you prefer.
Now to wrap up the post. For more information about SDL_GetRelativeMouseState, and SDL_GetMouseState, visit http://sdl.beuc.net/sdl.wiki/SDL_GetRelativeMouseState. For other tutorials about mouse input in SDL, check out http://lazyfoo.net/SDL_tutorials/lesson09/index.php (caution: C++ concepts in use) and http://interhacker.wordpress.com/2012/08/27/chapter-6-mouse-input-in-sdl/.
Thank you for reading, the next post will cover BMP images.
No comments:
Post a Comment