In this lesson, we will cover in detail how we can detect and react to user input.
Input is handled through the SDL event queue, so user input will show up within our event loop.
There, we can inspect the event and, if it’s something we want to react to, we can write the required code to make that happen.
SDL supports a wide range of input, including game pads, joysticks and microphones. Here, we’ll be focused on keyboard and mouse events.
Keyboard input is slightly easier to handle than the mouse, as there are so few events to deal with. A button is either pressed, or it isn’t. However, there are some additional considerations when it comes to determining which button was pressed.
SDL_KEYDOWN
, SDL_KEYUP
)The two most common event types for handling the keyboard are SDL_KEYDOWN
, which is created when a button is pressed, and SDL_KEYUP
, for when a button is released.
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_KEYDOWN) {
// ...
} else if (Event.type == SDL_KEYDOWN) {
// ...
}
}
To find out which button was pressed or released, we need to check the key code. When we’re dealing with a keyboard event, the key code is available within Event.key.keysym.sym
.
This is an integer, but SDL provides named helpers to make our code more readable. For example, here is how we’d detect the arrow keys being pressed:
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_KEYDOWN) {
if (Event.key.keysym.sym == SDLK_UP) {
// Up Arrow
} else if (Event.key.keysym.sym == SDLK_DOWN) {
// Down Arrow
} else if (Event.key.keysym.sym == SDLK_LEFT) {
// Left Arrow
} else if (Event.key.keysym.sym == SDLK_RIGHT) {
// Right Arrow
}
}
}
All the key codes are available in the official documentation.
SDL_GetKeyName
If we have a keycode, and we want to understand what key was pressed, the SDL_GetKeyName
function can help us. It converts a key code to a more understandable name:
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_KEYDOWN) {
std::cout
<< "Key Pressed! Key Code: "
<< Event.key.keysym.sym
<< ", Key Name: "
<< SDL_GetKeyName(Event.key.keysym.sym)
<< '\n';
}
}
Key Pressed! Key Code: 113, Key Name: Q
Key Pressed! Key Code: 119, Key Name: W
Key Pressed! Key Code: 101, Key Name: E
Key Pressed! Key Code: 114, Key Name: R
Key Pressed! Key Code: 116, Key Name: T
Key Pressed! Key Code: 121, Key Name: Y
There are a lot of mouse-related events we might be interested in. Here, we cover the most common examples
SDL_MOUSEBUTTONDOWN
, SDL_MOUSEBUTTONUP
)When any mouse button is pressed or released, we get one of these events.
while (SDL_PollEvent(&Event)) {
if (Event.type == *SDL_MOUSEBUTTONDOWN*) {
// A button was pressed
} else if (Event.type == *SDL_MOUSEBUTTONUP*) {
// A button was released
}
}
When we have a mouse button event, we can find out which button was pressed or released within the Event.button.button
value. As usual, SDL has helpers to compare these to:
if (Event.type == SDL_MOUSEBUTTONDOWN) {
if (Event.button.button == SDL_BUTTON_LEFT) {
// Left Button Pressed
} else if (Event.button.button == SDL_BUTTON_RIGHT) {
// Left Button Released
}
}
SDL_MOUSEMOTION
)When the user’s mouse was moved, an event with a type of SDL_MOUSEMOTION
is created. Within that event, the mouse’s x
and y
position are available under Event.motion.x
:
while (SDL_PollEvent(&Event)) {
if (Event.type == *SDL_MOUSEMOTION*) {
int x { Event.motion.x };
int y { Event.motion.y };
}
}
By default, these values are relative to the window, starting from the top left.
So, if x
is 0 and y
is 0, that means the user moved their cursor to the extreme top left of our window.
If x
is the same as our window width, and y
is the same as our window height, that means the cursor moved to the bottom right of our window.
SDL_MOUSEWHEEL
)We can react to mouse wheel scrolling by listening for events with the type of SDL_MOUSEWHEEL
.
Event.wheel.y
lets us know how far the wheel was scrolled. Positive values mean the user scrolled down; negative means they scrolled up.
while (SDL_PollEvent(&Event)) {
if (Event.type == *SDL_MOUSEWHEEL*) {
int Amount { Event.wheel.y };
}
}
SDL_WINDOWEVENT_ENTER
, SDL_WINDOWEVENT_LEAVE
)Finally, we can detect when the user’s cursor entered or left our window, by listening for the SDL_WINDOWEVENT
 type.
This is a slightly more complicated event type, as there are multiple different subtypes. The subtypes are available under Event.window.event
.
The mouse enter and mouse leave events have a subtype of SDL_WINDOWEVENT_ENTER
and SDL_WINDOWEVENT_LEAVE
:
if (Event.type == SDL_WINDOWEVENT) {
if (Event.window.event == SDL_WINDOWEVENT_ENTER) {
// Cursor entered our window
} else if (Event.window.event == SDL_WINDOWEVENT_LEAVE) {
// Cursor left our window
}
}
In many applications, particularly games, we want to prevent the user’s mouse cursor from leaving our window.
That can be done with the SDL_SetWindowMouseGrab
function. We need to pass it a pointer to our SDL_Window
, as well as a second argument instructing whether we want to grab the cursor or not.
The second argument should be SDL_TRUE
to grab the cursor, or SDL_FALSE
to release it.
Here, we grab the cursor when the user presses their left mouse button, and we release it when they release the button.
This code assumes the pointer to our SDL_Window
is stored in a variable called SDLWindow
:
while (SDL_PollEvent(&Event)) {
if (
Event.type == *SDL_MOUSEBUTTONDOWN &&*
Event.button.button == SDL_BUTTON_LEFT
) {
SDL_SetWindowMouseGrab(SDLWindow, SDL_TRUE);
}
else if (
Event.type == *SDL_MOUSEBUTTONDOWN &&*
Event.button.button == SDL_BUTTON_LEFT
) {
SDL_SetWindowMouseGrab(SDLWindow, SDL_FALSE);
}
}
Similar to trapping the cursor, we also often want to hide it. That can be done with SDL_ShowCursor
, passing in SDL_ENABLE
to show the cursor, or SDL_DISABLE
to hide it:
if (shouldShowCursor){
SDL_ShowCursor(SDL_ENABLE);
} else {
SDL_ShowCursor(SDL_DISABLE);
}
We do not need to wait for an event to occur before we can get information about the mouse and keyboard. At any time, we can find out what is going on.
SDL_GetMouseState
SDL_GetMouseState
accepts two int
pointers, which it will update with the mouse’s current x
and y
 coordinates.
int x, y;
SDL_GetMouseState(&x, &y);
std::cout << "Mouse is at " << x << ", " << y;
Just like with mouse events, these values are relative to our window. If we don’t care about the mouse position, and only want to know about the buttons, we can pass nullptr
to both of these parameters.
To get the state of the buttons, we use the return value of SDL_GetMouseState
. The function returns bit flags / a bit mask in the form of a Uint32
. We can use the Bitwise OR
operator (&
) on this value to determine if any buttons are pressed.
SDL provides helpers to use with this bit mask - most notably, SDL_BUTTON_LMASK
for the left button, and SDL_BUTTON_RMASK
for the right button.
Here’s an example where we get and print the mouse state when the user presses their space bar:
while (SDL_PollEvent(&Event)) {
if (
Event.type == SDL_KEYDOWN &&
Event.key.keysym.sym == SDLK_SPACE
) {
int x, y;
Uint32 Buttons { SDL_GetMouseState(&x, &y) };
std::cout << "Mouse is at " << x << ", " << y;
if ((Buttons & SDL_BUTTON_LMASK)) {
std::cout << " - Left Button is pressed";
}
if ((Buttons & SDL_BUTTON_RMASK)) {
std::cout << " - Right Button is pressed";
}
std::cout << "\n";
}
}
Mouse is at 71, 43
Mouse is at 139, 100 - Left Button is pressed
Mouse is at 77, 254 - Left Button is pressed - Right Button is pressed
SDL_GetKeyboardState
Getting keyboard state works a little differently to mouse state. SDL maintains an array of UInt8
values - one for every keyboard key. The integer at each array position is 1
if the corresponding key is pressed, and 0
if it isn’t.
Once we acquire a pointer to that array, we can access it any time we need. The array is kept updated at all times by SDL - we don’t need to call a function to get the latest state.
We get the pointer using SDL_GetKeyboardState
:
int Length;
const Uint8* KeyboardState = SDL_GetKeyboardState(&Length);
This function accepts a pointer to an integer, which will be populated with the length of the array. If we don’t care about that, we can just pass nullptr
.
Once we have access to the array, we then check if a key is pressed by indexing into that array. The index we need to use will be the scan code of the key we’re interested in.
Just like key codes, SDL provides helper identifiers for those scan codes. To check if the space bar is pressed, we would do this:
if (KeyboardState[SDL_SCANCODE_SPACE]) {
std::cout << "Space is pressed\n";
}
All the scan codes are available in the official documentation.
What follows is an example application, showing how we can react to input, and manipulate the SDL window in various ways.
We’ve provided a more fleshed-out Window
class, which builds on what we created in the previous lesson.
Using additional SDL functions, which we’ve documented in the comments, we’ve added some additional functionality:
// Window.h
#pragma once
#include <SDL.h>
#include <string>
class Window {
public:
Window() {
SDL_Init(SDL_INIT_VIDEO);
SDLWindow = SDL_CreateWindow(
"Hello World",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
windowWidth, windowHeight, 0
);
SDLWindowSurface = SDL_GetWindowSurface(SDLWindow);
}
void Update() {
SDL_FillRect(
SDLWindowSurface,
nullptr,
SDL_MapRGB(
SDLWindowSurface->format,
bgRed, bgGreen, bgBlue
)
);
}
void RenderFrame() {
SDL_UpdateWindowSurface(SDLWindow);
}
void SetBackgroundColor(int R, int G, int B) {
bgRed = R;
bgGreen = G;
bgBlue = B;
}
void SetTitle(std::string NewTitle) {
// https://wiki.libsdl.org/SDL_SetWindowTitle
SDL_SetWindowTitle(
SDLWindow, NewTitle.c_str()
);
}
void ChangeWindowSize(int Amount) {
// https://wiki.libsdl.org/SDL_SetWindowSize
SDL_SetWindowSize(
SDLWindow,
windowWidth += Amount,
windowHeight += Amount
);
}
[[nodiscard]]
int GetWindowWidth() const {
return windowWidth;
}
[[nodiscard]]
int GetWindowHeight() const {
return windowHeight;
}
int MoveRelative(int x, int y) {
// https://wiki.libsdl.org/SDL_GetWindowPosition
int CurrentX; int CurrentY;
SDL_GetWindowPosition(
SDLWindow, &CurrentX, &CurrentY
);
// https://wiki.libsdl.org/SDL_SetWindowPosition
SDL_SetWindowPosition(
SDLWindow, CurrentX + x, CurrentY + y
);
}
void GrabMouse() {
// https://wiki.libsdl.org/SDL_SetWindowMouseGrab
SDL_SetWindowMouseGrab(SDLWindow, SDL_TRUE);
}
void FreeMouse() {
// https://wiki.libsdl.org/SDL_SetWindowMouseGrab
SDL_SetWindowMouseGrab(SDLWindow, SDL_FALSE);
}
private:
SDL_Window* SDLWindow { nullptr };
SDL_Surface* SDLWindowSurface { nullptr };
int windowWidth { 300 };
int windowHeight { 300 };
int bgRed { 40 };
int bgGreen { 40 };
int bgBlue { 40 };
};
To make use of this in main.cpp
, we’ve built our event loop with lots of examples:
#include <SDL.h>
#include <iostream>
#include "Window.h"
int main() {
Window AppWindow;
auto KeyboardState = SDL_GetKeyboardState(nullptr);
SDL_Event Event;
while(true) {
while (SDL_PollEvent(&Event)) {
// System
if (Event.type == SDL_QUIT) [[unlikely]] {
SDL_Quit();
return 0;
}
// Mouse Input
else if (Event.type == SDL_MOUSEBUTTONDOWN) {
if (Event.button.button == SDL_BUTTON_LEFT) {
SDL_ShowCursor(SDL_ENABLE);
AppWindow.GrabMouse();
} else if (Event.button.button == SDL_BUTTON_RIGHT) {
if (KeyboardState[SDL_SCANCODE_SPACE]) {
std::cout << "Spacebar is pressed\n";
} else {
std::cout << "Spacebar is not pressed\n";
}
}
} else if (Event.type == SDL_MOUSEBUTTONUP) {
if (Event.button.button == SDL_BUTTON_LEFT) {
SDL_ShowCursor(SDL_ENABLE);
AppWindow.FreeMouse();
}
} else if (Event.type == SDL_MOUSEWHEEL) {
AppWindow.ChangeWindowSize(Event.wheel.y * 3);
AppWindow.SetTitle("Focused");
} else if (Event.type == SDL_MOUSEMOTION) [[likely]] {
AppWindow.SetBackgroundColor(
Event.motion.x * 255 / AppWindow.GetWindowWidth(),
Event.motion.y * 255 / AppWindow.GetWindowHeight(),
0
);
} else if (Event.type == SDL_WINDOWEVENT) {
if (
Event.window.event == SDL_WINDOWEVENT_ENTER
) {
AppWindow.SetTitle("Focused");
} else if (
Event.window.event == SDL_WINDOWEVENT_LEAVE
) {
AppWindow.SetTitle("Unfocused");
}
}
// Keyboard Input
else if (Event.type == SDL_KEYDOWN) {
if (Event.key.keysym.sym == SDLK_UP) {
AppWindow.MoveRelative(0, -10);
} else if (Event.key.keysym.sym == SDLK_DOWN) {
AppWindow.MoveRelative(0, 10);
} else if (Event.key.keysym.sym == SDLK_LEFT) {
AppWindow.MoveRelative(-10, 0);
} else if (Event.key.keysym.sym == SDLK_RIGHT) {
AppWindow.MoveRelative(10, 0);
} else if (Event.key.keysym.sym == SDLK_RETURN) {
int x, y;
Uint32 Buttons { SDL_GetMouseState(&x, &y) };
std::cout << "Mouse is at " << x << ", " << y;
if ((Buttons & SDL_BUTTON_LMASK)) {
std::cout << " - Left Button is pressed";
}
if ((Buttons & SDL_BUTTON_RMASK)) {
std::cout << " - Right Button is pressed";
}
std::cout << "\n";
} else {
std::cout
<< "Key Pressed! Key Code: "
<< Event.key.keysym.sym
<< ", Key Name: "
<< SDL_GetKeyName(Event.key.keysym.sym)
<< '\n';
}
}
}
AppWindow.Update();
AppWindow.RenderFrame();
}
}
Up next, we'll introduce SDL rectangles and colours. Combined with our new knowledge of events, we'll combine these concepts to start creating a user interface!
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games