Keyboard and Mouse Input in SDL2

Learn how SDL2 handles user input, and how we can react to it to update our application in real time.
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
aSDL8.jpg
Ryan McCombe
Ryan McCombe
Posted

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.

Handling the Keyboard in SDL2

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.

SDL2 Keyboard Button Events (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.

Converting an SDL2 Key Code to Key Name with 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

Handling the Mouse in SDL2

There are a lot of mouse-related events we might be interested in. Here, we cover the most common examples

SDL2 Mouse Click Events (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
   }
}

SDL2 Mouse Motion Events (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 Mouse Wheel Events - Detecting Mouse Scroll (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 };
  }
}

Detecting Mouse Enter / Mouse Leave (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
  }
}

Preventing the Cursor from leaving the Window in SDL2 (C++)

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);
  }
}

Hiding the Cursor in SDL2 (C++)

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);
}

Getting the Current Mouse and Keyboard State in SDL2

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.

SDL2 Mouse State with 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

SDL2 Keyboard State with 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.

A Full Example

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:

  • Changing the window’s background color
  • Getting and changing the window’s size
  • Changing the window’s position
  • Changing the window's title
  • Grabbing and releasing the mouse
// 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:

  • The window title changes when we move our cursor into and out of the window
  • The window background color changes based on our mouse position
  • The scroll wheel makes the window larger or smaller
  • The arrow keys move the window around
  • When the left mouse button is held, the cursor is hidden and locked to the window
  • When the right mouse button is pressed, we find and log out information about the keyboard state
  • When the return key is pressed, we find and log out information about the mouse state
  • When any other key is pressed, we log out its name
#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!

Was this lesson useful?

Next Lesson

Creating a Simple UI Architecture

How to build a simple, scalable and layered UI architecture using an SDL2 and C++ event loop
aSDL9.jpg
Ryan McCombe
Ryan McCombe
Posted
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Next Lesson

Creating a Simple UI Architecture

How to build a simple, scalable and layered UI architecture using an SDL2 and C++ event loop
aSDL9.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved