Building on our basic SDL window setup, this lesson introduces interactivity by focusing on mouse input.
We'll explore how SDL represents mouse actions through its event system. You'll learn to detect when the user moves the mouse, clicks buttons, or even moves the cursor into or out of the application window.
Key topics include:
SDL_MOUSEMOTION
events to get cursor coordinates.SDL_MOUSEBUTTONDOWN
, SDL_MOUSEBUTTONUP
).SDL_WINDOWEVENT_ENTER
, SDL_WINDOWEVENT_LEAVE
).We'll start with the basic SDL application structure from our previous lessons. This includes initializing SDL, creating a window using our Window class, and setting up the main event loop. The code below provides this foundation:
#include <SDL.h>
#include "Window.h"
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_QUIT) {
SDL_Quit();
return 0;
}
}
GameWindow.Render();
GameWindow.Update();
}
return 0;
}
#pragma once
#include <iostream>
#include <SDL.h>
class Window {
public:
Window() {
SDLWindow = SDL_CreateWindow(
"Hello Window",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
700, 300, 0
);
}
void Render() {
SDL_FillRect(
GetSurface(),
nullptr,
SDL_MapRGB(
GetSurface()->format, 50, 50, 50
)
);
}
void Update() {
SDL_UpdateWindowSurface(SDLWindow);
}
SDL_Surface* GetSurface() const {
return SDL_GetWindowSurface(SDLWindow);
}
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
~Window() {
if (SDLWindow && SDL_WasInit(SDL_INIT_VIDEO)) {
SDL_DestroyWindow(SDLWindow);
}
}
private:
SDL_Window* SDLWindow{nullptr};
};
As a reminder, 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 event loop.
Every time the player moves their mouse within our window, an SDL_Event
is pushed onto the event queue.
That event will have a type of SDL_MOUSEMOTION
, and the mouse’s new position is available through the x
and y
members of the motion
object:
// main.cpp
#include <SDL.h>
#include "Window.h"
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_MOUSEMOTION) {
std::cout << "Mouse Motion Detected - "
<< "x: " << E.motion.x
<< ", y: " << E.motion.y << '\n';
} else if (E.type == SDL_QUIT) {
SDL_Quit();
return 0;
}
}
GameWindow.Render();
GameWindow.Update();
}
return 0;
}
Mouse Motion Detected - x: 2, y: 77
Mouse Motion Detected - x: 6, y: 79
Mouse Motion Detected - x: 8, y: 80
When working with SDL, and pixel data in general, the coordinate system that is used tends to be different to what we might expect.
It’s common that the origin - that is, the position $(0, 0)$ - represents the top left of the window, surface, or image. Increasing x
values corresponds to moving right, and increasing y
values corresponds to moving down.
So, if our window was 700 units wide and 300 units tall, it’s coordinate system would look like this:
For example, if we have a mouse motion event with an x
value of 300
, that means the pointer was moved to a position that is 300 units from the left edge of our window. If y
is 200
, that means the pointer is 200 pixels from the top edge of the window.
This is sometimes called a y-down coordinate system. The system we typically use in other contexts, and we learn about in geometry class, is y-up
We cover coordinate systems and moving objects between them in much more detail later in the course.
Our program can have multiple windows open at the same time. The x
and y
variables of the motion
object tell us where the cursor moved to within a window, but not which window the cursor is hovering over.
We cover multi-window applications later in the course but, outside of those lessons, we’ll assume that our program only has a single window.
If we need to work with positions relative to some other location, such as the bottom-right of our window, that tends to be easy to work out with some arithmetic.
However, this arithmetic requires us to know the width and height of the window. We can add getters to our Window
class to support that:
// Window.h
// ...
class Window {
public:
Window() {
SDLWindow = SDL_CreateWindow(
"Scene",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
GetWidth(), GetHeight(),
0
);
}
int GetWidth() const { return 700; }
int GetHeight() const { return 300; }
// ...
};
Note that this code assumes our window is not resizable. We cover window sizing and resizing in a dedicated chapter later in the course.
With our getters in place, any code with access to our window can now easily retrieve it’s dimensions when needed:
if (E.type == SDL_MOUSEMOTION) {
int DistanceFromLeftEdge{E.motion.x};
int DistanceFromTopEdge{E.motion.y};
int DistanceFromRightEdge{
GameWindow.GetWidth() - E.motion.x};
int DistanceFromBottomEdge{
GameWindow.GetHeight() - E.motion.y};
}
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:
// main.cpp
#include <SDL.h>
#include "Window.h"
void HandleMotion(SDL_MouseMotionEvent& E) {
int DistanceFromLeft{E.x};
int DistanceFromTop{E.y};
// ...
}
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_MOUSEMOTION) {
HandleMotion(E.motion);
} else if (E.type == SDL_QUIT) {
SDL_Quit();
return 0;
}
}
GameWindow.Render();
GameWindow.Update();
}
return 0;
}
By default, SDL will only report mouse motion events when our window has mouse focus - that is, when the user’s cursor is hovering over our window.
When the user’s cursor enters or leaves our window, SDL provides events to notify us of this. These events have a type
of SDL_WINDOWEVENT
.
while(SDL_PollEvent(&E)) {
if (E.type == SDL_WINDOWEVENT) {
// ...
}
}
However, a wide range of other events are categorized under this SDL_WINDOWEVENT
type too, which we’ll cover in more detail later.
If our event is an SDL_WINDOWEVENT
, we can access 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 represents the user’s cursor entering our 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";
} else if (E.window.event == SDL_WINDOWEVENT_LEAVE) {
std::cout << "Mouse Left Window\n";
}
}
SDL_WindowEvent
Similar to SDL_MouseMotionEvent
, a dedicated SDL_WindowEvent
type is available for window events, which is helpful if we want to send that event to some other function.
If the outer SDL_Event
has a type of SDL_WINDOWEVENT
, then the window
variable of that event will contain the SDL_WindowEvent
:
#include <SDL.h>
#include "Window.h"
void HandleWindowEvent(SDL_WindowEvent& E) {
if (E.event == SDL_WINDOWEVENT_ENTER) {
std::cout << "Mouse Entered Window\n";
} else if (E.event == SDL_WINDOWEVENT_LEAVE) {
std::cout << "Mouse Left Window\n";
}
}
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_WINDOWEVENT) {
HandleWindowEvent(E.window);
} else if (E.type == SDL_QUIT) {
SDL_Quit();
return 0;
}
}
GameWindow.Render();
GameWindow.Update();
}
return 0;
}
In our previous examples, we’re giving our event handlers different names - HandleMotion()
and HandleWindowEvent()
- based on the type of events they are for. However, this is not entirely necessary as the functions also have different parameter types - SDL_MouseMotionEvent
and SDL_WindowEvent
.
We can give our functions the same name if we prefer, and let the compiler select the correct one based on the argument provided in our event loop:
#include <SDL.h>
#include "Window.h"
void HandleEvent(SDL_MouseMotionEvent& E) {
// ...
}
void HandleEvent(SDL_WindowEvent& E) {
// ...
}
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_MOUSEMOTION) {
HandleEvent(E.motion);
} else if (E.type == SDL_WINDOWEVENT) {
HandleEvent(E.window);
} else if (E.type == SDL_QUIT) {
SDL_Quit();
return 0;
}
}
GameWindow.Render();
GameWindow.Update();
}
return 0;
}
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(&E)) {
if (E.type == SDL_MOUSEBUTTONDOWN) {
std::cout << "Button Pressed\n";
} else if (E.type == SDL_MOUSEBUTTONUP) {
std::cout << "Button Released\n";
}
}
Both of these event types include an SDL_MouseButtonEvent
in the button
variable:
#include <SDL.h>
#include "Window.h"
void HandleButtonEvent(SDL_MouseButtonEvent& E) {
// ...
}
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {
if (
E.type == SDL_MOUSEBUTTONDOWN ||
E.type == SDL_MOUSEBUTTONUP
) {
HandleButtonEvent(E.button);
} else if (E.type == SDL_QUIT) {
SDL_Quit();
return 0;
}
}
GameWindow.Render();
GameWindow.Update();
}
return 0;
}
This SDL_MouseButtonEvent
also includes the type
value, so we can still determine if it corresponds to a button being pressed or released. Alternatively, we can access the state
variable, and compare it to SDL_PRESSED
or SDL_RELEASED
:
#include <SDL.h>
#include "Window.h"
void HandleButtonEvent(SDL_MouseButtonEvent& E) {
if (E.type == SDL_MOUSEBUTTONDOWN) {
// Button Pressed
} else if (E.type == SDL_MOUSEBUTTONUP) {
// Button Released
}
// Alternatively:
if (E.state == SDL_PRESSED) {
// Button Pressed
} else if (E.state == SDL_RELEASED) {
// Button Released
}
}
int main(int argc, char** argv) {/*...*/}
To understand which button was pressed or released, the SDL_MouseButtonEvent
includes a button
member, storing a numeric identifier for the mouse button that triggered the event.
SDL provides some helper values to compare this identifier to the common mouse buttons:
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 second extra mouse button, which is on the side of some miceUsing these values in code might look something like this:
#include <SDL.h>
#include "Window.h"
void HandleButtonEvent(SDL_MouseButtonEvent& E) {
if (E.button == SDL_BUTTON_LEFT) {
// The left button was pressed or released
} else if (E.button == SDL_BUTTON_RIGHT) {
// The right button was pressed or released
}
if (
E.button == SDL_BUTTON_LEFT &&
E.state == SDL_PRESSED
) {
// The left button was pressed
}
}
int main(int argc, char** argv) {/*...*/}
SDL_Event
is a UnionSDL_Event
is an example of a union. With a union, all members are stored in the same memory location.
This is used when we want a variable to store one of several possible types. In the SDL_Event
case, those types are things like SDL_MouseMotionEvent
, SDL_WindowEvent
, and SDL_MouseButtonEvent
.
We cover unions, and modern, safer alternatives to them in our advanced course:
For now, a key point to note about unions is that they’re not type safe. That is, we don’t know what data type is stored in that memory address. If get it wrong by, for example, assuming an SDL_MouseButtonEvent
is stored there when it’s actually an SDL_WindowEvent
, then the compiler can’t detect that error - we just have a bug.
Before we do almost anything with an SDL_Event
, we should ascertain exactly what type it is pointing at. We do this by checking the type
field, as shown in our earlier examples. The following code violates this principle:
void HandleEvent(SDL_Event& E) {
bool isLeftClick{
E.button.button == SDL_BUTTON_LEFT &&
E.type == SDL_MOUSEBUTTONDOWN
};
}
This is accessing the E.button.button
variable before we confirmed that the memory address really is storing an SDL_MouseButtonEvent
. If it’s not, our program will have a bug.
To fix this, we should change the order of our operands to check the type
first, and only access the button
member once we know it’s an SDL_MouseButtonEvent
:
void HandleEvent(SDL_Event& E) {
bool isLeftClick{
E.type == SDL_MOUSEBUTTONDOWN &&
E.button.button == SDL_BUTTON_LEFT
};
}
As a reminder, C++, like most programming languages, performs short circuit evaluation. In the second example, if E.type == SDL_MOUSEBUTTONDOWN
is false
, then E.button.button == SDL_BUTTON_LEFT
is never evaluated.
With the &&
operator, if the left operand is false
, then the entire expression will be false
. It doesn’t matter what the right operand is, so it’s effectively ignored.
If our application needs to detect double clicks, we can check the clicks
member variable of the SDL_MouseButtonEvent
. This represents the number of times the user has clicked that same button in quick succession:
void HandleButtonEvent(const SDL_MouseButtonEvent& E) {
if (E.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 clickIt will also detect double-clicks of any button. We’ll often want to filter on the specific user action we care about:
#include <SDL.h>
#include "Window.h"
void HandleButtonEvent(SDL_MouseButtonEvent& E) {
if (E.button == SDL_BUTTON_LEFT &&
E.state == SDL_PRESSED &&
E.clicks >= 2
) {
// The left button was double-clicked
}
}
int main(int argc, char** argv) {/*...*/}
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.
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 we have access to, including:
We have a full chapter dedicated to mouse handling later in the course.
Here's the complete code incorporating all the mouse event handling techniques discussed in this lesson. It demonstrates how to detect motion, window enter/leave events, button clicks, and double clicks using simple std::cout
statements for illustration.
While the specific output functions like HandleMotionEvent()
won't be carried forward into subsequent lessons, the fundamental principles – checking event types (Event.type
), accessing specific event data (Event.motion
, Event.window
, Event.button
), and processing input within the event loop – are crucial concepts that we will build upon throughout the rest of the course.
#include <SDL.h>
#include "Window.h"
void HandleMotionEvent(
SDL_MouseMotionEvent& E,
Window& GameWindow
) {
std::cout << "Mouse Motion Detected - "
<< "x: " << E.x
<< ", y: " << E.y;
std::cout << "\n Distance from Right: "
<< GameWindow.GetWidth() - E.x;
std::cout << "\n Distance from Bottom: "
<< GameWindow.GetHeight() - E.y << '\n';
}
void HandleWindowEvent(SDL_WindowEvent& E) {
if (E.event == SDL_WINDOWEVENT_ENTER) {
std::cout << "Mouse Entered Window\n";
} else if (E.event == SDL_WINDOWEVENT_LEAVE) {
std::cout << "Mouse Left Window\n";
}
}
void HandleButtonEvent(SDL_MouseButtonEvent& E) {
if (E.button == SDL_BUTTON_RIGHT) {
std::cout << "Right Click or Release\n";
}
if (E.button == SDL_BUTTON_LEFT &&
E.state == SDL_PRESSED &&
E.clicks >= 2
) {
std::cout << "Left Double Click\n";
}
}
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_MOUSEMOTION) {
HandleMotionEvent(E.motion, GameWindow);
} else if (E.type == SDL_WINDOWEVENT) {
HandleWindowEvent(E.window);
} else if (
E.type == SDL_MOUSEBUTTONDOWN ||
E.type == SDL_MOUSEBUTTONUP
) {
HandleButtonEvent(E.button);
} else if (E.type == SDL_QUIT) {
SDL_Quit();
return 0;
}
}
GameWindow.Render();
GameWindow.Update();
}
return 0;
}
#pragma once
#include <iostream>
#include <SDL.h>
class Window {
public:
Window() {
SDLWindow = SDL_CreateWindow(
"Scene",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
GetWidth(), GetHeight(), 0
);
}
int GetWidth() const { return 700; }
int GetHeight() const { return 300; }
~Window() {
if (SDLWindow) {
SDL_DestroyWindow(SDLWindow);
}
}
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
void Render() {
SDL_FillRect(
GetSurface(), nullptr,
SDL_MapRGB(GetSurface()->format,
220, 220, 220));
}
void Update() {
SDL_UpdateWindowSurface(SDLWindow);
}
SDL_Surface* GetSurface() const {
return SDL_GetWindowSurface(SDLWindow);
}
private:
SDL_Window* SDLWindow;
};
Mouse Motion Detected - x: 200, y: 124
Distance from Right: 500
Distance from Bottom: 176
Left Double Click
Right Click or Release
Right Click or Release
We've explored how to make SDL applications responsive to mouse input. By examining the SDL_Event
structure within the main loop, we can detect and react to mouse movements, button clicks (including double clicks), and window boundary crossings by the cursor. This forms the basis for implementing mouse controls.
Key Takeaways:
while(SDL_PollEvent(&E))
loop.E.type
(e.g., SDL_MOUSEMOTION
, SDL_MOUSEBUTTONDOWN
).E.motion
, E.button
).E.motion.x
and E.motion.y
(y-down).SDL_BUTTON_LEFT
, SDL_BUTTON_RIGHT
.E.button.state
(SDL_PRESSED
, SDL_RELEASED
).E.button.clicks
.SDL_WINDOWEVENT
.SDL_MouseMotionEvent
exist for specific event types.Discover how to process mouse input, including position tracking and button presses
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games