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:
SDL_WINDOW_MOUSE_GRABBED
SDL_SetWindowMouseGrab
SDL_SetWindowMouseRect
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.
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);
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
}
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;
}
}
}
}
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:
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.
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)
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();
}
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
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) {/*...*/}
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:
SDL_SetWindowMouseGrab
Implement mouse constraints in SDL2 to control cursor movement using window grabs and rectangular bounds
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games