Mouse Input Basics
Discover how to process mouse input, including position tracking and button presses
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 moves the cursor into or out of the application window.
Key topics include:
- Processing
SDL_EVENT_MOUSE_MOTION
events to get cursor coordinates. - Understanding SDL's coordinate system.
- Detecting mouse button presses and releases (
SDL_EVENT_MOUSE_BUTTON_DOWN
,SDL_EVENT_MOUSE_BUTTON_UP
). - Identifying which mouse button was clicked.
- Handling double clicks.
- Responding to the cursor entering or leaving the window (
SDL_EVENT_WINDOW_MOUSE_ENTER
,SDL_EVENT_WINDOW_MOUSE_LEAVE
).
Starting Point
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:
Files
As a reminder, our SDL_PollEvent(&Event)
statement will update our event object Event
with any mouse action that the user performs. We'll then detect and react to those actions within the body of the event loop.
Mouse Motion Events
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_EVENT_MOUSE_MOTION
. In SDL3, the mouse's new position is available as floating-point values through the x
and y
members of the motion
object:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
bool IsRunning = true;
SDL_Event Event;
while (IsRunning) {
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_MOUSE_MOTION) {
std::cout << "Mouse Motion Detected - "
<< "x: " << Event.motion.x
<< ", y: " << Event.motion.y << '\n';
} else if (Event.type == SDL_EVENT_QUIT) {
IsRunning = false;
}
}
GameWindow.Render();
GameWindow.Update();
}
SDL_Quit();
return 0;
}
Mouse Motion Detected - x: 2.5, y: 77.1
Mouse Motion Detected - x: 6.2, y: 79.8
Mouse Motion Detected - x: 8.9, y: 80.3
SDL's Coordinate System
When working with SDL, and pixel data in general, the coordinate system that is used tends to be different from what we might expect.
It's common that the origin - that is, the position - represents the top left of the window, surface, or image. Increasing x
values correspond to moving right, and increasing y
values correspond to moving down.
So, if our window were 700 units wide and 300 units tall, its 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.
Position Arithmetic
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:
src/Window.h
#pragma once
#include <SDL3/SDL.h>
class Window {
public:
Window() {
SDLWindow = SDL_CreateWindow(
"Scene",
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 its dimensions when needed:
if (Event.type == SDL_EVENT_MOUSE_MOTION) {
float DistanceFromLeftEdge{Event.motion.x};
float DistanceFromTopEdge{Event.motion.y};
float DistanceFromRightEdge{
GameWindow.GetWidth() - Event.motion.x};
float DistanceFromBottomEdge{
GameWindow.GetHeight() - Event.motion.y};
}
Using SDL_MouseMotionEvent
When we're dealing with an SDL_Event
that has a type of SDL_EVENT_MOUSE_MOTION
, 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:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
void HandleMotion(SDL_MouseMotionEvent& Event) {
float DistanceFromLeft{Event.x};
float DistanceFromTop{Event.y};
// ...
}
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
bool IsRunning = true;
SDL_Event Event;
while (IsRunning) {
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_MOUSE_MOTION) {
HandleMotion(Event.motion);
} else if (Event.type == SDL_EVENT_QUIT) {
IsRunning = false;
}
}
GameWindow.Render();
GameWindow.Update();
}
SDL_Quit();
return 0;
}
Mouse Enter / Mouse Leave Events
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.
In SDL3, when the user's cursor enters or leaves our window, SDL provides specific top-level events to notify us. These events have a type
of SDL_EVENT_WINDOW_MOUSE_ENTER
and SDL_EVENT_WINDOW_MOUSE_LEAVE
respectively.
We can check for these directly in our event loop:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
bool IsRunning = true;
SDL_Event Event;
while (IsRunning) {
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_WINDOW_MOUSE_ENTER) {
std::cout << "Mouse Entered Window\n";
} else if (Event.type == SDL_EVENT_WINDOW_MOUSE_LEAVE) {
std::cout << "Mouse Left Window\n";
} else if (Event.type == SDL_EVENT_QUIT) {
IsRunning = false;
}
}
GameWindow.Render();
GameWindow.Update();
}
SDL_Quit();
return 0;
}
Mouse Click Events
When any mouse button is pressed or released, we get an SDL_Event
whose type
is equal to SDL_EVENT_MOUSE_BUTTON_DOWN
or SDL_EVENT_MOUSE_BUTTON_UP
respectively:
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
std::cout << "Button Pressed\n";
} else if (Event.type == SDL_EVENT_MOUSE_BUTTON_UP) {
std::cout << "Button Released\n";
}
}
Both of these event types include an SDL_MouseButtonEvent
in the button
variable:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
void HandleButtonEvent(SDL_MouseButtonEvent& Event) {
// ...
}
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
bool IsRunning = true;
SDL_Event Event;
while (IsRunning) {
while (SDL_PollEvent(&Event)) {
if (
Event.type == SDL_EVENT_MOUSE_BUTTON_DOWN ||
Event.type == SDL_EVENT_MOUSE_BUTTON_UP
) {
HandleButtonEvent(Event.button);
} else if (Event.type == SDL_EVENT_QUIT) {
IsRunning = false;
}
}
GameWindow.Render();
GameWindow.Update();
}
SDL_Quit();
return 0;
}
Button Pressed vs Button Released
This SDL_MouseButtonEvent
also includes the type
value, so we can still determine if it corresponds to a button being pressed or released. Alternatively, in SDL3 we can access the down
boolean variable, which is true
for a press and false
for a release:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
void HandleButtonEvent(SDL_MouseButtonEvent& Event) {
if (Event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
// Button Pressed
} else if (Event.type == SDL_EVENT_MOUSE_BUTTON_UP) {
// Button Released
}
// Alternatively:
if (Event.down) {
// Button Pressed
} else {
// Button Released
}
}
int main(int, char**) {/*...*/}
Which Button was Pressed or Released?
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 mice
Using these values in code might look something like this:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
void HandleButtonEvent(SDL_MouseButtonEvent& Event) {
if (Event.button == SDL_BUTTON_LEFT) {
// The left button was pressed or released
} else if (Event.button == SDL_BUTTON_RIGHT) {
// The right button was pressed or released
}
if (
Event.button == SDL_BUTTON_LEFT &&
Event.down
) {
// The left button was pressed
}
}
int main(int, char**) {/*...*/}
The SDL_Event
Union
SDL_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
and SDL_MouseButtonEvent
.
We cover unions, and modern, safer alternatives to them in .
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 we get it wrong, for example, by assuming an SDL_MouseButtonEvent
is stored there when it's actually some other event type, 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& Event) {
bool isLeftClick{
Event.button.button == SDL_BUTTON_LEFT &&
Event.type == SDL_EVENT_MOUSE_BUTTON_DOWN
};
}
This is accessing the Event.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& Event) {
bool isLeftClick{
Event.type == SDL_EVENT_MOUSE_BUTTON_DOWN &&
Event.button.button == SDL_BUTTON_LEFT
};
}
As a reminder, C++, like most programming languages, performs short-circuit evaluation. In the second example, if Event.type == SDL_EVENT_MOUSE_BUTTON_DOWN
is false
, then Event.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.
Double Clicks
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& Event) {
if (Event.clicks >= 2) {
// Double Click Detected
}
}
Note that the clicks
variable will also be set on the SDL_EVENT_MOUSE_BUTTON_UP
event, so the code in this if
block will be executed twice:
- On the
SDL_EVENT_MOUSE_BUTTON_DOWN
event of the second click - On the
SDL_EVENT_MOUSE_BUTTON_UP
event of the second click
It will also detect double-clicks of any button. We'll often want to filter on the specific user action we care about:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
void HandleButtonEvent(SDL_MouseButtonEvent& Event) {
if (Event.button == SDL_BUTTON_LEFT &&
Event.down &&
Event.clicks >= 2
) {
// The left button was double-clicked
}
}
int main(int, char**) {/*...*/}
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.
Reminder: Function Overloading
In our previous examples, we're giving our event handlers different names - such as HandleMotion()
and HandleButtonEvent()
- based on the type of events they are for. However, this is not entirely necessary as the functions also have different parameter types, like SDL_MouseMotionEvent
and SDL_MouseButtonEvent
.
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:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"
void HandleEvent(SDL_MouseMotionEvent& Event) {
// ...
}
void HandleEvent(SDL_MouseButtonEvent& Event) {
// ...
}
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
bool IsRunning = true;
SDL_Event Event;
while (IsRunning) {
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_EVENT_MOUSE_MOTION) {
HandleEvent(Event.motion);
} else if (
Event.type == SDL_EVENT_MOUSE_BUTTON_DOWN ||
Event.type == SDL_EVENT_MOUSE_BUTTON_UP
) {
HandleEvent(Event.button);
} else if (Event.type == SDL_EVENT_QUIT) {
IsRunning = false;
}
}
GameWindow.Render();
GameWindow.Update();
}
SDL_Quit();
return 0;
}
Complete Code
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.button
), and processing input within the event loop - are crucial concepts that we will build upon throughout the rest of the course.
Files
Mouse Motion Detected - x: 200.4, y: 124.1
Distance from Right: 499.6
Distance from Bottom: 175.9
Left Double Click
Right Click or Release
Right Click or Release
Summary
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:
- The core of input handling is the
while(SDL_PollEvent(&Event))
loop. - Filter events based on
Event.type
(e.g.,SDL_EVENT_MOUSE_MOTION
,SDL_EVENT_MOUSE_BUTTON_DOWN
). - Access event-specific data using union members (e.g.,
Event.motion
,Event.button
). - Mouse position is given by
Event.motion.x
andEvent.motion.y
(y-down, as floats). - Button identity uses constants like
SDL_BUTTON_LEFT
,SDL_BUTTON_RIGHT
. - Button state is checked via
Event.button.down
(true
for pressed,false
for released). - Double clicks are identified using
Event.button.clicks
. - Window enter/leave events are top-level events (
SDL_EVENT_WINDOW_MOUSE_ENTER
,SDL_EVENT_WINDOW_MOUSE_LEAVE
). - Dedicated structs like
SDL_MouseMotionEvent
exist for specific event types.
Rectangles and SDL_Rect
Learn to create, render, and interact with basic rectangles using the SDL_Rect
and SDL_Color
types.