Handling Mouse Input

Learn how to detect and handle mouse input events in SDL, including mouse motion, button clicks, and window entry/exit.
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

In this lesson, we will cover in detail how we can detect and react to the two main forms of mouse input - the user moving their cursor, and the user clicking their mouse buttons.

When these forms of input are detected, an SDL_Event is pushed onto the event queue. We can capture these events through our event loop, and handle them as needed.

This lesson builds on our earlier work, where we have a Window class that initializes SDL and creates a window, and an application loop set up in our main function:

#include <SDL.h>

class Window {/*...*/}; int main(int argc, char** argv) { SDL_Init(SDL_INIT_VIDEO); Window GameWindow; SDL_Event Event; while(true) { while(SDL_PollEvent(&Event)) { // Detect and handle input events // ... } GameWindow.RenderFrame(); } SDL_Quit(); return 0; }

Our SDL_PollEvent(&Event) statement will update our Event object with any mouse action that the user performs. We’ll then detect and react to those actions within the body of the loop.

Mouse Motion Events

When the user’s mouse is moved, an event with a type of SDL_MOUSEMOTION is created. Within that event, the mouse’s x and y positions are available under Event.motion:

while (SDL_PollEvent(&Event)) {
  if (Event.type == SDL_MOUSEMOTION) {
    std::cout << "x: " << Event.motion.x
      << ", y: " << Event.motion.y << '\n';
  }
}
x: 619, y: 201
x: 643, y: 214
x: 667, y: 228
x: 676, y: 234

By default, these values are relative to the window, starting from the top left.

So for example, if x is 30, the pointer was moved to a position that is 30 pixels from the left edge of our window. If y is 40, that means the pointer is 40 pixels from the top edge of the window.

We can calculate the distance from the right and bottom edges with some arithmetic involving our window’s width and height respectively:

void MousePosition(const SDL_Event& E) {
  int DistanceFromLeft{E.motion.x};
  int DistanceFromTop{E.motion.y};
  
  int DistanceFromRight{WindowWidth - E.motion.x};
  int DistanceFromBottom{WindowHeight - E.motion.y};
}

Later in the course, we’ll introduce more ways of tracking mouse motion, including handling scenarios where the mouse pointer is outside of our window.

SDL_MouseMotionEvent

When we’re dealing with an SDL_Event that has a type of SDL_MOUSEMOTION, the motion struct it contains has the SDL_MouseMotionEvent type.

When working with mouse motion events, this object is slightly easier to use than the top-level SDL_Event, as it does not require us to access the intermediate motion subobject to retrieve the information we care about.

This makes it useful for storing and transferring mouse motion events. The event loop body can get very large, so to mitigate this, we can move our mouse motion handler to a standalone function, and pass the Event.motion struct to it from our event loop:

// Event Loop
while (SDL_PollEvent(&Event)) {
  if (Event.type == SDL_MOUSEMOTION) {
    HandleMotion(Event.motion);
  }
}
// Handler
void HandleMotion(const SDL_MouseMotionEvent& E) {
  int DistanceFromLeft{E.x};
  int DistanceFromTop{E.y};
  
  int DistanceFromRight{WindowWidth - E.x};
  int DistanceFromBottom{WindowHeight - E.y};
}

Mouse Click Events

When any mouse button is pressed or released, we get an SDL_Event whose type is equal to SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP respectively.

while (SDL_PollEvent(&Event)) {
  if (Event.type == SDL_MOUSEBUTTONDOWN) {
    std::cout << "Button Pressed\n";
  } else if (Event.type == SDL_MOUSEBUTTONUP) {
    std::cout << "Button Released\n";
  }
}
Button Pressed
Button Released

When we have a mouse button event, we can find out which button was pressed or released within the Event.button.button value. This is an integer, and SDL includes 5 other integer variables to compare it against:

  • SDL_BUTTON_LEFT - The left mouse button
  • SDL_BUTTON_RIGHT - The right mouse button
  • SDL_BUTTON_MIDDLE - The middle mouse button
  • SDL_BUTTON_X1 - The first extra mouse button, which is on the side of some mice
  • SDL_BUTTON_X2 - The first extra mouse button, which is on the side of some mice

We can use the variables like this:

if (Event.type == SDL_MOUSEBUTTONDOWN) {
   if (Event.button.button == SDL_BUTTON_LEFT) {
     std::cout << "Left Button Pressed\n";
   } else if (Event.button.button == SDL_BUTTON_RIGHT) {
     std::cout << "Right Button Pressed\n";
   }
}
Left Button Pressed
Right Button Pressed

Mouse Click Position

If we’re interested in where the mouse was when the user clicked a button, we don’t need to track mouse motion events separately. The SDL_MouseButtonEvent includes x and y coordinates representing where the cursor was when the click occurred.

Similar to motion events, by default, the x coordinate is the distance from the left edge of the window, and the y coordinate represents the distance from the top edge of the window:

void MousePosition(const SDL_Event& E) {
  int DistanceFromLeft{E.button.x};
  int DistanceFromTop{E.button.y};
  
  int DistanceFromRight{WindowWidth - E.button.x};
  int DistanceFromBottom{WindowHeight - E.button.y};
  
  // ...
}

SDL_MouseButtonEvent

When we have an SDL_Event that has a type of SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP, the button struct within the event has the SDL_MouseButtonEvent type. This type is slightly easier to use than the more generic SDL_Event, as it does not require us to access the intermediate button subobject to retrieve the information we care about.

As such, when we’re storing or transferring a mouse button event to be processed elsewhere, the button subobject of the SDL_Event is typically what we use:

// Event Loop
while(SDL_PollEvent(&Event)) {
  if (Event.type == SDL_MOUSEBUTTONDOWN ||
      Event.type == SDL_MOUSEBUTTONUP) {
    HandleButton(Event.button);
  }
}
// Handler
void HandleButton(const SDL_MouseButtonEvent& E) {  
  if (E.button == SDL_BUTTON_LEFT) {
    std::cout << "Left Button\n";
  } else if (E.button == SDL_BUTTON_RIGHT) {
    std::cout << "Right Button\n";
  }
  
  int DistanceFromLeft{E.x};
  int DistanceFromTop{E.y};

  // ...
}
Left Button
Left Button
Right Button
Right Button

When we have an SDL_MouseButtonEvent object, we can understand if it represents a button press or a button release by accessing the type member. As before, we can compare this type to SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP.

Alternatively, we can access the state variable, and compare it to SDL_PRESSED or SDL_RELEASED:

void HandleButton(const SDL_MouseButtonEvent& E) {
  if (Event.type == SDL_MOUSEBUTTONDOWN) {
    // Button Pressed
  } else if (Event.type == SDL_MOUSEBUTTONUP) {
    // Button Released
  }
  
  // Equivalently:
  if (Event.state == SDL_PRESSED) {
    // Button Pressed
  } else if (Event.type == SDL_RELEASED) {
    // Button Released
  }
}

Double Clicks

If our application needs to detect double clicks, we can check the clicks member variable of the SDL_MouseButtonEvent, or button.clicks of the SDL_Event. This represents the number of times the user has clicked that same button in quick succession:

void HandleButton(const SDL_MouseButtonEvent& E) {
  if (Event.clicks == 2) {
    // Double Click Detected
  }
}

Note that the clicks variable will also be set on the SDL_MOUSEBUTTONUP event, so the code in this if block will be executed twice:

  1. On the SDL_MOUSEBUTTONDOWN event of the second click
  2. On the SDL_MOUSEBUTTONUP event of the second click

We’ll often want to filter on the specific event we care about:

void HandleButton(const SDL_MouseButtonEvent& E) {  
  if (E.type == SDL_MOUSEBUTTONDOWN &&
    E.clicks == 2) {
    // Double Click Detected
  }
}

Using the clicks variable tends to be preferred over trying to create double-click detection from scratch, as it’s more complex than it first appears. Additionally, most operating systems allow users to change their double-click sensitivity and, by using SDL’s solution, our application can respect those settings.

Mouse Enter / Mouse Leave Events

Finally, we may want to detect when the user’s cursor entered or left our window. To do this, we first need to detect events whose type matches SDL_WINDOWEVENT:

while(SDL_PollEvent(&Event)) {
  if (Event.type == SDL_WINDOWEVENT) {
    // ...
  }
}

A wide range of events is categorized under the SDL_WINDOWEVENT type. If our event has this type, we can access the more specific information under the window struct of that SDL_Event. This subobject has another field called event, which gives us a more specific categorization.

If the window event was raised because the user’s cursor entered the window, the window.event member will be equal to SDL_WINDOWEVENT_ENTER. When the event represents the cursor leaving the window, it will be equal to SDL_WINDOWEVENT_LEAVE:

if (E.type == SDL_WINDOWEVENT) {
  if (E.window.event == SDL_WINDOWEVENT_ENTER) {
    std::cout << "Mouse Entered Window\n";
  }
  if (E.window.event == SDL_WINDOWEVENT_LEAVE) {
    std::cout << "Mouse Left Window\n";
  }
}
Mouse Entered Window
Mouse Left Window

SDL_WindowEvent

Similar to the other event categories, a standalone SDL_WindowEvent type is available, which can help us store and transfer events whose type is within the SDL_WINDOWEVENT category. The window member of a window event has this type:

// Event Loop
while (SDL_PollEvent(&E)) {
  if (E.type == SDL_WINDOWEVENT) {
    HandleWindowEvent(E.window);
  }
}
// Handler
void HandleWindowEvent(const SDL_WindowEvent& E) {  
  if (E.event == SDL_WINDOWEVENT_ENTER) {
    std::cout << "Mouse Entered Window\n";
  }
  if (E.event == SDL_WINDOWEVENT_LEAVE) {
    std::cout << "Mouse Left Window\n";
  }
}

Preview: Advanced Mouse Options

This lesson covers the minimal basics of mouse handling, so we can start to build practical projects as quickly as possible. There are many more powerful options that SDL provides, including:

  • Accepting input from the mouse scroll wheel
  • Querying the mouse state on demand, without using events
  • Hiding, showing, and customizing the cursor
  • Mouse grabbing, for when we want to prevent the cursor from leaving the window
  • Relative mouse mode, for when we want to create "mouse look" style systems such as those used in first-person shooters

We cover these topics in more detail later in the course

Summary

In this lesson, we explored how to detect and handle different types of mouse input in SDL. Key topics included:

  • Detecting mouse motion events and retrieving cursor positions.
  • Handling mouse button events for clicks and releases.
  • Managing mouse enter and leave events for window interaction.

Was this lesson useful?

Next Lesson

Building a Modular UI System

Learn how to create a flexible and extensible UI system using C++ and SDL, focusing on component hierarchy and polymorphism.
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Handling Mouse Input

Learn how to detect and handle mouse input events in SDL, including mouse motion, button clicks, and window entry/exit.

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
Implementing User Interaction
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:

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

Building a Modular UI System

Learn how to create a flexible and extensible UI system using C++ and SDL, focusing on component hierarchy and polymorphism.
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved