Mouse State

Learn how to monitor mouse position and button states in real-time using SDL3's state query functions

Ryan McCombe
Updated

Previously, we introduced how SDL dispatches events when the player moves their mouse within our window, or when they click their mouse buttons when our window has input focus:

Files

src
Select a file to view its content
Mouse moved to (226, 4)
Left button pressed at (226, 4)
Mouse moved to (227, 4)
Mouse moved to (228, 5)
Left button released at (228, 5)

This allows us to react to our user's mouse input, but it is not the only approach that we can use. Instead of reacting to events, we are free to query the mouse's current position and which buttons are pressed at any time.

We covered how to track mouse motion and clicking through the event loop in our introductory lesson on .

This lesson covers techniques for tracking the mouse independently of the event loop, and practical examples of why we'd want to.

Using SDL_GetMouseState()

SDL_GetMouseState accepts two float pointers, which it will update with the mouse's current x and y coordinates.

float x, y;
SDL_GetMouseState(&x, &y);
std::cout << "Mouse is at " << x << ", " << y;

Just like with mouse events, these values are relative to the top left of the window. If the player's cursor is at the extreme top left corner, then x and y will be updated to have values 0.0f and 0.0f.

To get the state of the buttons, we use the return value of SDL_GetMouseState(). The function returns a 32-bit unsigned integer representing a bit mask:

float x, y;
Uint32 Buttons{SDL_GetMouseState(&x, &y)};

SDL3 provides the SDL_MouseButtonFlags alias for Uint32, allowing us to be more descriptive about what this variable represents:

float x, y;
SDL_MouseButtonFlags Buttons{SDL_GetMouseState(&x, &y)};

A bit mask uses individual bits within a number to store multiple yes/no (boolean) values efficiently. Think of it like a row of switches - each bit represents one switch that can be either on (1) or off (0).

In our case, each bit represents whether a specific mouse button is pressed (1) or not pressed (0). This lets us track multiple button states using a single number. SDL provides helper macros to use with these bit masks, allowing us to isolate the bit representing the button we're interested in:

  • SDL_BUTTON_MASK(SDL_BUTTON_LEFT) checks if the left button is pressed
  • SDL_BUTTON_MASK(SDL_BUTTON_RIGHT) checks if the right button is pressed

We use the & (bitwise AND) operator to test if a specific button is pressed:

float x, y;
SDL_MouseButtonFlags Buttons{SDL_GetMouseState(&x, &y)};

bool LeftPressed{
  Buttons & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)};
bool RightPressed{
  Buttons & SDL_BUTTON_MASK(SDL_BUTTON_RIGHT)};

We cover bit masks and the & operator in more detail in our .

Passing nullptr to SDL_GetMouseState()

If we don't care about the mouse's x or y position, we can pass nullptr to either (or both) of those parameters:

float x, y;

// We only care about the x (horizontal) position
SDL_GetMouseState(&x, nullptr);

// We only care about the y (vertical) position
SDL_GetMouseState(nullptr, &y);

SDL_MouseButtonFlags Buttons{
  // We only care about the buttons
  SDL_GetMouseState(nullptr, nullptr)
};

Example: Retrieving Mouse State on Keyboard Event

Naturally, if we wanted to be notified when the player moves their mouse or clicks a mouse button, we'd monitor the event loop for those specific events.

Querying the state of the mouse directly using SDL_GetMouseState() is for scenarios when we need to understand what's going on with the mouse when some other event happens.

For example, we might have a gameplay behavior where the player shoots in the direction of their cursor when they press a button on their keyboard. Rather than tracking the cursor continuously through the event loop, we can instead just wait for the space bar event, and then find out where the cursor is.

Here's an example where we get and print the mouse state when the user presses their space bar:

src/main.cpp

#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Window.h"

void HandleKeyboardEvent(const SDL_KeyboardEvent& E) {
  if (E.key == SDLK_SPACE) {
    float x, y;
    SDL_MouseButtonFlags Buttons{SDL_GetMouseState(&x, &y)};

    std::cout << "\nMouse Position: "
      << x << ", " << y;
    if (Buttons & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)) {
      std::cout << " - Left Button is pressed";
    }
    if (Buttons & SDL_BUTTON_MASK(SDL_BUTTON_RIGHT)) {
      std::cout << " - Right Button is pressed";
    }
  }
}

int main(int, char**) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  SDL_Event Event;

  bool IsRunning = true;
  while (IsRunning) {
    while (SDL_PollEvent(&Event)) {
      if (Event.type == SDL_EVENT_KEY_DOWN) {
        HandleKeyboardEvent(Event.key);
      } else if (Event.type == SDL_EVENT_QUIT) {
        IsRunning = false;
      }
    }
    GameWindow.Render();
    GameWindow.Update();
  }

  SDL_Quit();
  return 0;
}
Mouse Position: 289, 101
Mouse Position: 525, 150 - Left Button is pressed
Mouse Position: 64, 210 - Left Button is pressed - Right Button is pressed

Getting Mouse State from Tick Functions

Another use case for querying the mouse state rather than using the event loop is for game objects that need to continuously track the mouse.

For example, we might be working on a drag-and-drop interaction that needs to continuously track the cursor whilst the player is holding down their mouse button.

In these scenarios, we'd typically use those objects' Tick() function to implement the required behaviors:

src/main.cpp

#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Window.h"

class Object {
 public:
  void Tick() {
    float x, y;
    Uint32 Buttons{SDL_GetMouseState(&x, &y)};

    std::cout << "\nMouse Position: "
      << x << ", " << y;
    if (Buttons & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)) {
      std::cout << " - Left Button is pressed";
    }
    if (Buttons & SDL_BUTTON_MASK(SDL_BUTTON_RIGHT)) {
      std::cout << " - Right Button is pressed";
    }
  }
};

int main(int, char**) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  Object GameObject;
  SDL_Event Event;

  bool IsRunning = true;
  while (IsRunning) {
    while (SDL_PollEvent(&Event)) {
      if (Event.type == SDL_EVENT_QUIT) {
        IsRunning = false;
      }
    }
    GameWindow.Render();
    GameObject.Tick(); 
    GameWindow.Update();
  }

  SDL_Quit();
  return 0;
}
Mouse Position: 308, 108
Mouse Position: 308, 107
Mouse Position: 308, 107
Mouse Position: 308, 106 - Left Button is pressed
Mouse Position: 308, 106 - Left Button is pressed
Mouse Position: 307, 106 - Left Button is pressed - Right Button is pressed

Remember, the more logic we have executing in Tick() functions, the slower our application will take to produce each frame, reducing responsiveness.

We should strive to only perform these checks when they're required. One possible solution is a simple if check:

// ...
class Object {
public:
  void Tick() {
    // Return early if tracking isn't enabled
    if (!shouldTrack) return;

    // Track Mouse
    // ...
  }
private:
  // Only enable tracking when necessary
  bool shouldTrack{false};
};

Mouse State and Focus

By default, SDL will stop tracking our mouse when our pointer leaves the window. The x and y values updated by SDL_GetMouseState() will continue to report the position the cursor had before leaving the window.

In most situations, objects that are using this function to continuously track the mouse will want to pause that tracking when the cursor is outside the window.

As we covered in the previous lesson, we can monitor the event loop and examine top-level window events to understand when the cursor enters and leaves our window:

src/main.cpp

#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Window.h"

class Object {
public:
  void HandleEvent(const SDL_Event& E) {
    if (E.type == SDL_EVENT_WINDOW_MOUSE_ENTER) {
      std::cout << "\nTracking Resumed";
      shouldTrack = true;
    } else if (E.type == SDL_EVENT_WINDOW_MOUSE_LEAVE) {
      std::cout << "\nTracking Paused";
      shouldTrack = false;
    }
  }

  void Tick() {
    if (shouldTrack) { HandleMouse(); }
  }

private:
  bool shouldTrack{true};

  void HandleMouse() {
    float x, y;
    Uint32 Buttons{SDL_GetMouseState(&x, &y)};

    std::cout << "\nMouse Position: "
      << x << ", " << y;
    if (Buttons & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)) {
      std::cout << " - Left Button is pressed";
    }
    if (Buttons & SDL_BUTTON_MASK(SDL_BUTTON_RIGHT)) {
      std::cout << " - Right Button is pressed";
    }
  }
};

int main(int, char**) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  Object GameObject;
  SDL_Event Event;

  bool IsRunning = true;
  while (IsRunning) {
    while (SDL_PollEvent(&Event)) {
      if (Event.type == SDL_EVENT_QUIT) {
        IsRunning = false;
      }
      GameObject.HandleEvent(Event); 
    }
    
    GameWindow.Render();
    GameObject.Tick(); 
    GameWindow.Update();
  }

  SDL_Quit();
  return 0;
}
Mouse Position: 25, 196
Mouse Position: 25, 196
Tracking Paused
Tracking Resumed
Mouse Position: 14, 238
Mouse Position: 14, 238

Summary

SDL provides functions for querying mouse state directly, giving us flexibility in how we handle mouse input beyond just responding to events. Key topics covered:

  • Using SDL_GetMouseState() to get current mouse position and button states
  • Working with bit masks to check mouse button states
  • Understanding when to use state queries vs event handling
  • Handling mouse tracking when cursor leaves window
Have a question about this lesson?
Answers are generated by AI models and may not be accurate