Mouse Input Constraints

Implement mouse constraints in SDL2 to control cursor movement using window grabs and rectangular bounds
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
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

SDL2 provides tools for controlling mouse movement in our applications. In this lesson, we'll explore how to constrain the mouse cursor to specific windows and regions, giving you precise control over user input. Key topics covered:

  • Window-level mouse grabbing using SDL_WINDOW_MOUSE_GRABBED
  • Dynamic mouse grab toggling with SDL_SetWindowMouseGrab
  • Rectangular constraints using SDL_SetWindowMouseRect
  • Combining grab and rectangle constraints
  • Error handling and state verification

Mouse Grabbing

In many applications, particularly games, we want to prevent the user’s mouse cursor from leaving a window. There is a flag within the SDL_WindowFlags bit set dedicated to this purpose. The SDL_WINDOW_MOUSE_GRABBED bit mask can be used to help us select this bit:

SDL_CreateWindow(
  "Hello Window",
  100, 200, 300, 400,
  SDL_WINDOW_MOUSE_GRABBED 
);

The window flags are kept up to date throughout the lifecycle of the window. As such, we can check if a window is currently grabbing the mask by retrieving its flags, and selecting the SDL_WINDOW_MOUSE_GRABBED bit:

bool isGrabbed(SDL_Window* Window) {
  return SDL_GetWindowFlags(Window)
    & SDL_WINDOW_MOUSE_GRABBED;
}

We cover window flags, including the use of the bitwise AND operator & in more detail here:

SDL_GetWindowMouseGrab()

We can alternatively determine if a window is grabbing the mouse by passing its pointer to SDL_GetWindowMouseGrab():

bool isGrabbed(SDL_Window* Window) {
  return SDL_GetWindowMouseGrab(Window);
}

SDL_GetGrabbedWindow()

We can retrieve the window that has currently grabbed the mouse using the SDL_GetGrabbedWindow(). This returns the SDL_Window pointer to the window that has grabbed the mouse, or a nullptr if the mouse is not grabbed:

SDL_Window* Grabbed{SDL_GetGrabbedWindow()};

if (Grabbed) {
  std::cout << "Mouse grabbed by: "
    << SDL_GetWindowTitle(Grabbed);
} else {
  std::cout << "Mouse is not grabbed";
}

This function is primarily useful in programs that have multiple windows, which we’ll cover in more detail later in this chapter.

Toggling Mouse Grab

That can be done with the SDL_SetWindowMouseGrab() function. We need to pass it a pointer to the SDL_Window, as well as a second argument indicating whether we want the mouse to grab.

The second argument should be SDL_TRUE to grab the cursor, or SDL_FALSE to release it:

// Grab the mouse
SDL_SetWindowMouseGrab(SDLWindow, SDL_TRUE);

// Release the mouse
SDL_SetWindowMouseGrab(SDLWindow, SDL_FALSE);

Error Checking

Mouse grabbing may not always succeed due to system limitations or configurations. While SDL doesn't return error codes for these operations directly, we can verify if the grab was successful by checking the window's state afterward.

Below, we check this immediately after attempting to create a window with mouse grab enabled:

SDL_Window* Window{
  SDL_CreateWindow(
    "Hello Window",
    100, 200, 300, 400,
    SDL_WINDOW_MOUSE_GRABBED
  )
};

if (!SDL_GetWindowMouseGrab(Window)) {
  std::cout << "Mouse grab failed - system "
    "may not support this feature\n";
  // Handle the failure appropriately
}

The same verification applies when toggling the mouse grab for an existing window:

SDL_SetWindowMouseGrab(Window, SDL_TRUE);
if (!SDL_GetWindowMouseGrab(Window)) {
  std::cout << "Mouse grab failed - system "
    "may not support this feature\n";
  // Handle the failure appropriately
}

Example

In the following example, we grab the cursor and constrain it to our window as long as the player is holding their left mouse button. When they release the button, we release their mouse:

#include <SDL.h>
#include "Window.h"

void HandleMouseEvent(
  SDL_MouseButtonEvent& E, SDL_Window* Window
) {
  if (E.button != SDL_BUTTON_LEFT) return;

  if (E.type == SDL_MOUSEBUTTONDOWN) {
    SDL_SetWindowMouseGrab(Window, SDL_TRUE);
  } else if (E.type == SDL_MOUSEBUTTONUP) {
    SDL_SetWindowMouseGrab(Window, SDL_FALSE);
  }
}

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) {
        HandleMouseEvent(
          E.button, GameWindow.SDLWindow
        );
      } else if (E.type == SDL_QUIT) {
        SDL_Quit();
        return 0;
      }
    }
  }
}

Constraining the Cursor within a Window

We can be more specific with our cursor constraint by restricting it to only a specific area of our window. To do this, we use the SDL_SetWindowMouseRect() function, passing a pointer to the SDL_Window and a pointer to the SDL_Rect.

An SDL_Rect is a simple structure that defines a rectangle using 4 integers:

  1. The horizontal position of the top left corner of the rectangle
  2. The vertical position of the top left corner of the rectangle
  3. The width of the rectangle
  4. The height of the rectangle

Below, we constrain the cursor to a 100x100 pixel square at the top left corner of our window:

SDL_Window* Window{
  SDL_CreateWindow(
    "Hello Window",
    100, 200, 300, 400, 0
  )
};

SDL_Rect Rect{0, 0, 100, 100};
SDL_SetWindowMouseRect(Window, &Rect);

SDL_SetWindowMouseRect() copies the SDL_Rect immediately, so we don’t need to ensure that our copy survives. It can safely be deleted after the SDL_SetWindowMouseRect() invocation and our cursor will remain constrained until we invoke the function again with a different pointer.

Releasing the Cursor

We can delete a window mouse rectangle we previously set by passing a nullptr to the second argument of SDL_SetWindowMouseRect():

SDL_SetWindowMouseRect(Window, nullptr);

This will free the player’s mouse to navigate anywhere within our window (or anywhere at all, if our window hasn’t grabbed their mouse)

Error Checking

SDL_SetWindowMouseRect() is not always supported. It returns 0 if setting the window rectangle was successful, or a negative number if it failed. We can call SDL_GetError() for an explanation of why it failed:

if(SDL_SetWindowMouseRect(Window, nullptr) < 0) {
  std::cout << "Setting Window Rect Failed: "
    << SDL_GetError();
}

Getting the Mouse Rectangle

We can retrieve the mouse rectangle associated with a window by passing the SDL_Window* to SDL_GetWindowMouseRect().

This returns a pointer to the constant SDL_Rect, or a nullptr if there is no rectangle confining the cursor within the window:

SDL_Window* Window{
  SDL_CreateWindow(
    "Hello Window",
    100, 200, 300, 400, 0
  )
};

SDL_Rect Rect{0, 0, 100, 100};
SDL_SetWindowMouseRect(Window, &Rect);

const SDL_Rect* WindowRect{
  SDL_GetWindowMouseRect(Window)};
  
if (WindowRect) {
  auto[x, y, w, h]{*WindowRect};
  std::cout << "Cursor is confined: "
    << x << ", " << y << ", " << w << ", " << h;
} else {
  std::cout << "Cursor is not confined";
}
Cursor is confined: 0, 0, 100, 100

Combining Mouse Grab with Mouse Rectangles

By default, setting the window mouse rectangle does not constrain the cursor to our window. Instead, it means that when the cursor is in our window (that is when our window has mouse focus), its position will be constrained to the rectangle provided.

If want to both constrain the cursor to our window, and specifically to a rectangle within that window, we can combine both SDL_SetWindowMouseRect() with mouse grabbing:

#include <SDL.h>
#include "Window.h"

void HandleMouseEvent(
  SDL_MouseButtonEvent& E, SDL_Window* Window
) {
  if (E.button != SDL_BUTTON_LEFT) return;

  if (E.type == SDL_MOUSEBUTTONDOWN) {
    SDL_Rect Rect{0, 0, 100, 100};
    SDL_SetWindowMouseGrab(Window, SDL_TRUE);
    SDL_SetWindowMouseRect(Window, &Rect);
  } else if (E.type == SDL_MOUSEBUTTONUP) {
    SDL_SetWindowMouseGrab(Window, SDL_FALSE);
    SDL_SetWindowMouseRect(Window, nullptr);
  }
}

int main(int argc, char** argv) {/*...*/}

Summary

SDL2 offers tools for controlling mouse movement, allowing us to create more advanced input-handling systems for our applications.

Through window grabbing and rectangular constraints, we can precisely control where users can move their cursor. Key takeaways:

  • Mouse grabbing can be enabled through window flags or SDL_SetWindowMouseGrab
  • Rectangular constraints allow fine-grained control over cursor movement
  • Both features can be combined for maximum control
  • Mouse constraints can be dynamically toggled based on game state
  • Attempts to constrain the mouse can fail, so we should consider adding error checking to react to these failures

Was this lesson useful?

Next Lesson

Mouse State

Learn how to monitor mouse position and button states in real-time using SDL's state query functions
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
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
Mouse Input
  • 60.GPUs and Rasterization
  • 61.SDL Renderers
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

This course includes:

  • 62 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Mouse State

Learn how to monitor mouse position and button states in real-time using SDL's state query functions
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved