In this lesson, we will cover in detail how we can detect and react to the two main forms of mouse input - the user moving their cursor, and the user clicking their mouse buttons.
When these forms of input are detected, an SDL_Event
is pushed onto the event queue. We can capture these events through our event loop, and handle them as needed.
This lesson builds on our earlier work, where we have a Window
class that initializes SDL and creates a window, and an application loop set up in our main
function:
#include <SDL.h>
class Window {/*...*/};
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event Event;
while(true) {
while(SDL_PollEvent(&Event)) {
// Detect and handle input events
// ...
}
GameWindow.RenderFrame();
}
SDL_Quit();
return 0;
}
Our SDL_PollEvent(&Event)
statement will update our Event
object with any mouse action that the user performs. We’ll then detect and react to those actions within the body of the loop.
When the user’s mouse is moved, an event with a type of SDL_MOUSEMOTION
is created. Within that event, the mouse’s x
and y
positions are available under Event.motion
:
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_MOUSEMOTION) {
std::cout << "x: " << Event.motion.x
<< ", y: " << Event.motion.y << '\n';
}
}
x: 619, y: 201
x: 643, y: 214
x: 667, y: 228
x: 676, y: 234
By default, these values are relative to the window, starting from the top left.
So for example, if x
is 30
, the pointer was moved to a position that is 30 pixels from the left edge of our window. If y
is 40, that means the pointer is 40 pixels from the top edge of the window.
We can calculate the distance from the right and bottom edges with some arithmetic involving our window’s width and height respectively:
void MousePosition(const SDL_Event& E) {
int DistanceFromLeft{E.motion.x};
int DistanceFromTop{E.motion.y};
int DistanceFromRight{WindowWidth - E.motion.x};
int DistanceFromBottom{WindowHeight - E.motion.y};
}
Later in the course, we’ll introduce more ways of tracking mouse motion, including handling scenarios where the mouse pointer is outside of our window.
SDL_MouseMotionEvent
When we’re dealing with an SDL_Event
that has a type of SDL_MOUSEMOTION
, the motion
struct it contains has the SDL_MouseMotionEvent
type.
When working with mouse motion events, this object is slightly easier to use than the top-level SDL_Event
, as it does not require us to access the intermediate motion
subobject to retrieve the information we care about.
This makes it useful for storing and transferring mouse motion events. The event loop body can get very large, so to mitigate this, we can move our mouse motion handler to a standalone function, and pass the Event.motion
struct to it from our event loop:
// Event Loop
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_MOUSEMOTION) {
HandleMotion(Event.motion);
}
}
// Handler
void HandleMotion(const SDL_MouseMotionEvent& E) {
int DistanceFromLeft{E.x};
int DistanceFromTop{E.y};
int DistanceFromRight{WindowWidth - E.x};
int DistanceFromBottom{WindowHeight - E.y};
}
When any mouse button is pressed or released, we get an SDL_Event
whose type
is equal to SDL_MOUSEBUTTONDOWN
or SDL_MOUSEBUTTONUP
respectively.
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_MOUSEBUTTONDOWN) {
std::cout << "Button Pressed\n";
} else if (Event.type == SDL_MOUSEBUTTONUP) {
std::cout << "Button Released\n";
}
}
Button Pressed
Button Released
When we have a mouse button event, we can find out which button was pressed or released within the Event.button.button
value. This is an integer, and SDL includes 5 other integer variables to compare it against:
SDL_BUTTON_LEFT
- The left mouse buttonSDL_BUTTON_RIGHT
- The right mouse buttonSDL_BUTTON_MIDDLE
- The middle mouse buttonSDL_BUTTON_X1
- The first extra mouse button, which is on the side of some miceSDL_BUTTON_X2
- The first extra mouse button, which is on the side of some miceWe can use the variables like this:
if (Event.type == SDL_MOUSEBUTTONDOWN) {
if (Event.button.button == SDL_BUTTON_LEFT) {
std::cout << "Left Button Pressed\n";
} else if (Event.button.button == SDL_BUTTON_RIGHT) {
std::cout << "Right Button Pressed\n";
}
}
Left Button Pressed
Right Button Pressed
If we’re interested in where the mouse was when the user clicked a button, we don’t need to track mouse motion events separately. The SDL_MouseButtonEvent
includes x
and y
coordinates representing where the cursor was when the click occurred.
Similar to motion events, by default, the x
coordinate is the distance from the left edge of the window, and the y
coordinate represents the distance from the top edge of the window:
void MousePosition(const SDL_Event& E) {
int DistanceFromLeft{E.button.x};
int DistanceFromTop{E.button.y};
int DistanceFromRight{WindowWidth - E.button.x};
int DistanceFromBottom{WindowHeight - E.button.y};
// ...
}
SDL_MouseButtonEvent
When we have an SDL_Event
that has a type of SDL_MOUSEBUTTONDOWN
or SDL_MOUSEBUTTONUP
, the button
struct within the event has the SDL_MouseButtonEvent
type. This type is slightly easier to use than the more generic SDL_Event
, as it does not require us to access the intermediate button
subobject to retrieve the information we care about.
As such, when we’re storing or transferring a mouse button event to be processed elsewhere, the button
subobject of the SDL_Event
is typically what we use:
// Event Loop
while(SDL_PollEvent(&Event)) {
if (Event.type == SDL_MOUSEBUTTONDOWN ||
Event.type == SDL_MOUSEBUTTONUP) {
HandleButton(Event.button);
}
}
// Handler
void HandleButton(const SDL_MouseButtonEvent& E) {
if (E.button == SDL_BUTTON_LEFT) {
std::cout << "Left Button\n";
} else if (E.button == SDL_BUTTON_RIGHT) {
std::cout << "Right Button\n";
}
int DistanceFromLeft{E.x};
int DistanceFromTop{E.y};
// ...
}
Left Button
Left Button
Right Button
Right Button
When we have an SDL_MouseButtonEvent
object, we can understand if it represents a button press or a button release by accessing the type
member. As before, we can compare this type to SDL_MOUSEBUTTONDOWN
or SDL_MOUSEBUTTONUP
.
Alternatively, we can access the state
variable, and compare it to SDL_PRESSED
or SDL_RELEASED
:
void HandleButton(const SDL_MouseButtonEvent& E) {
if (Event.type == SDL_MOUSEBUTTONDOWN) {
// Button Pressed
} else if (Event.type == SDL_MOUSEBUTTONUP) {
// Button Released
}
// Equivalently:
if (Event.state == SDL_PRESSED) {
// Button Pressed
} else if (Event.type == SDL_RELEASED) {
// Button Released
}
}
If our application needs to detect double clicks, we can check the clicks
member variable of the SDL_MouseButtonEvent
, or button.clicks
of the SDL_Event
. This represents the number of times the user has clicked that same button in quick succession:
void HandleButton(const SDL_MouseButtonEvent& E) {
if (Event.clicks == 2) {
// Double Click Detected
}
}
Note that the clicks
variable will also be set on the SDL_MOUSEBUTTONUP
event, so the code in this if
block will be executed twice:
SDL_MOUSEBUTTONDOWN
event of the second clickSDL_MOUSEBUTTONUP
event of the second clickWe’ll often want to filter on the specific event we care about:
void HandleButton(const SDL_MouseButtonEvent& E) {
if (E.type == SDL_MOUSEBUTTONDOWN &&
E.clicks == 2) {
// Double Click Detected
}
}
Using the clicks
variable tends to be preferred over trying to create double-click detection from scratch, as it’s more complex than it first appears. Additionally, most operating systems allow users to change their double-click sensitivity and, by using SDL’s solution, our application can respect those settings.
Finally, we may want to detect when the user’s cursor entered or left our window. To do this, we first need to detect events whose type matches SDL_WINDOWEVENT
:
while(SDL_PollEvent(&Event)) {
if (Event.type == SDL_WINDOWEVENT) {
// ...
}
}
A wide range of events is categorized under the SDL_WINDOWEVENT
type. If our event has this type
, we can access the more specific information under the window
struct of that SDL_Event
. This subobject has another field called event
, which gives us a more specific categorization.
If the window event was raised because the user’s cursor entered the window, the window.event
member will be equal to SDL_WINDOWEVENT_ENTER
. When the event represents the cursor leaving the window, it will be equal to SDL_WINDOWEVENT_LEAVE
:
if (E.type == SDL_WINDOWEVENT) {
if (E.window.event == SDL_WINDOWEVENT_ENTER) {
std::cout << "Mouse Entered Window\n";
}
if (E.window.event == SDL_WINDOWEVENT_LEAVE) {
std::cout << "Mouse Left Window\n";
}
}
Mouse Entered Window
Mouse Left Window
SDL_MouseWindowEvent
Similar to the other event categories, a standalone SDL_WindowEvent
type is available, which can help us store and transfer events whose type is within the SDL_WINDOWEVENT
category. The window
member of a window event has this type:
// Event Loop
while (SDL_PollEvent(&E)) {
if (E.type == SDL_WINDOWEVENT) {
HandleWindowEvent(E.window);
}
}
// Handler
void HandleWindowEvent(const SDL_WindowEvent& E) {
if (E.event == SDL_WINDOWEVENT_ENTER) {
std::cout << "Mouse Entered Window\n";
}
if (E.event == SDL_WINDOWEVENT_LEAVE) {
std::cout << "Mouse Left Window\n";
}
}
This lesson covers the minimal basics of mouse handling, so we can start to build practical projects as quickly as possible. There are many more powerful options that SDL provides, including:
We cover these topics in more detail later in the course
In this lesson, we explored how to detect and handle different types of mouse input in SDL. Key topics included:
Learn how to detect and handle mouse input events in SDL, including mouse motion, button clicks, and window entry/exit.
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games