Mouse Input Basics

Discover how to process mouse input, including position tracking and button presses

Ryan McCombe
Updated

Building on our basic SDL window setup, this lesson introduces interactivity by focusing on mouse input.

We'll explore how SDL represents mouse actions through its event system. You'll learn to detect when the user moves the mouse, clicks buttons, or even moves the cursor into or out of the application window.

Key topics include:

  • Processing SDL_MOUSEMOTION events to get cursor coordinates.
  • Understanding SDL's coordinate system.
  • Detecting mouse button presses and releases (SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP).
  • Identifying which mouse button was clicked.
  • Handling double clicks.
  • Responding to the cursor entering or leaving the windopullw (SDL_WINDOWEVENT_ENTER, SDL_WINDOWEVENT_LEAVE).

Starting Point

We'll start with the basic SDL application structure from our previous lessons. This includes initializing SDL, creating a window using our Window class, and setting up the main event loop. The code below provides this foundation:

As a reminder, 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 event loop.

Mouse Motion Events

Every time the player moves their mouse within our window, an SDL_Event is pushed onto the event queue.

That event will have a type of SDL_MOUSEMOTION, and the mouse's new position is available through the x and y members of the motion object:

// main.cpp
#include <SDL.h>
#include "Window.h"

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_MOUSEMOTION) {
        std::cout << "Mouse Motion Detected - "
          << "x: " << E.motion.x
          << ", y: " << E.motion.y << '\n';
      } else if (E.type == SDL_QUIT) {
        SDL_Quit();
        return 0;
      }
    }

    GameWindow.Render();
    GameWindow.Update();
  }

  return 0;
}
Mouse Motion Detected - x: 2, y: 77
Mouse Motion Detected - x: 6, y: 79
Mouse Motion Detected - x: 8, y: 80

SDL's Coordinate System

When working with SDL, and pixel data in general, the coordinate system that is used tends to be different to what we might expect.

It's common that the origin - that is, the position $(0, 0)$ - represents the top left of the window, surface, or image. Increasing x values corresponds to moving right, and increasing y values corresponds to moving down.

So, if our window was 700 units wide and 300 units tall, its coordinate system would look like this:

For example, if we have a mouse motion event with an x value of 300, that means the pointer was moved to a position that is 300 units from the left edge of our window. If y is 200, that means the pointer is 200 pixels from the top edge of the window.

This is sometimes called a y-down coordinate system. The system we typically use in other contexts, and we learn about in geometry class, is y-up

We cover coordinate systems and moving objects between them in much more detail later in the course.

Using 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:

// main.cpp
#include <SDL.h>
#include "Window.h"

void HandleMotion(SDL_MouseMotionEvent& E) {
  int DistanceFromLeft{E.x};
  int DistanceFromTop{E.y};
  // ...
}

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_MOUSEMOTION) {
        HandleMotion(E.motion);
      } else if (E.type == SDL_QUIT) {
        SDL_Quit();
        return 0;
      }
    }

    GameWindow.Render();
    GameWindow.Update();
  }

  return 0;
}

Mouse Enter / Mouse Leave Events

By default, SDL will only report mouse motion events when our window has mouse focus - that is, when the user's cursor is hovering over our window.

When the user's cursor enters or leaves our window, SDL provides events to notify us of this. These events have a type of SDL_WINDOWEVENT.

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

However, a wide range of other events are categorized under this SDL_WINDOWEVENT type too, which we'll cover in more detail later.

If our event is an SDL_WINDOWEVENT, we can access 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 represents the user's cursor entering our 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";
  } else if (E.window.event == SDL_WINDOWEVENT_LEAVE) {
    std::cout << "Mouse Left Window\n";
  }
}

Using SDL_WindowEvent

Similar to SDL_MouseMotionEvent, a dedicated SDL_WindowEvent type is available for window events, which is helpful if we want to send that event to some other function.

If the outer SDL_Event has a type of SDL_WINDOWEVENT, then the window variable of that event will contain the SDL_WindowEvent:

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

void HandleWindowEvent(SDL_WindowEvent& E) {
  if (E.event == SDL_WINDOWEVENT_ENTER) {
    std::cout << "Mouse Entered Window\n";
  } else if (E.event == SDL_WINDOWEVENT_LEAVE) {
    std::cout << "Mouse Left Window\n";
  }
}

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_WINDOWEVENT) {
        HandleWindowEvent(E.window);
      } else if (E.type == SDL_QUIT) {
        SDL_Quit();
        return 0;
      }
    }

    GameWindow.Render();
    GameWindow.Update();
  }

  return 0;
}

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(&E)) {
  if (E.type == SDL_MOUSEBUTTONDOWN) {
    std::cout << "Button Pressed\n";
  } else if (E.type == SDL_MOUSEBUTTONUP) {
    std::cout << "Button Released\n";
  }
}

Both of these event types include an SDL_MouseButtonEvent in the button variable:

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

void HandleButtonEvent(SDL_MouseButtonEvent& E) {
  // ...
}

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

    GameWindow.Render();
    GameWindow.Update();
  }

  return 0;
}

Button Pressed vs Button Released

This SDL_MouseButtonEvent also includes the type value, so we can still determine if it corresponds to a button being pressed or released. Alternatively, we can access the state variable, and compare it to SDL_PRESSED or SDL_RELEASED:

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

void HandleButtonEvent(SDL_MouseButtonEvent& E) {
  if (E.type == SDL_MOUSEBUTTONDOWN) {
    // Button Pressed
  } else if (E.type == SDL_MOUSEBUTTONUP) {
    // Button Released
  }
  
  // Alternatively:
  if (E.state == SDL_PRESSED) {
    // Button Pressed
  } else if (E.state == SDL_RELEASED) {
    // Button Released
  }
}

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

Which Button was Pressed or Released?

To understand which button was pressed or released, the SDL_MouseButtonEvent includes a button member, storing a numeric identifier for the mouse button that triggered the event.

SDL provides some helper values to compare this identifier to the common mouse buttons:

  • 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 second extra mouse button, which is on the side of some mice

Using these values in code might look something like this:

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

void HandleButtonEvent(SDL_MouseButtonEvent& E) {
  if (E.button == SDL_BUTTON_LEFT) {
    // The left button was pressed or released
  } else if (E.button == SDL_BUTTON_RIGHT) {
    // The right button was pressed or released
  }

  if (
    E.button == SDL_BUTTON_LEFT &&
    E.state == SDL_PRESSED
  ) {
    // The left button was pressed
  }
}

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

Double Clicks

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

void HandleButtonEvent(const SDL_MouseButtonEvent& E) {
  if (E.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

It will also detect double-clicks of any button. We'll often want to filter on the specific user action we care about:

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

void HandleButtonEvent(SDL_MouseButtonEvent& E) {
  if (E.button == SDL_BUTTON_LEFT &&
      E.state == SDL_PRESSED &&
      E.clicks >= 2
  ) {
    // The left button was double-clicked
  }
}

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

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.

Complete Code

Here's the complete code incorporating all the mouse event handling techniques discussed in this lesson. It demonstrates how to detect motion, window enter/leave events, button clicks, and double clicks using simple std::cout statements for illustration.

While the specific output functions like HandleMotionEvent() won't be carried forward into subsequent lessons, the fundamental principles - checking event types (Event.type), accessing specific event data (Event.motion, Event.window, Event.button), and processing input within the event loop - are crucial concepts that we will build upon throughout the rest of the course.

Mouse Motion Detected - x: 200, y: 124
  Distance from Right: 500
  Distance from Bottom: 176
Left Double Click
Right Click or Release
Right Click or Release

Summary

We've explored how to make SDL applications responsive to mouse input. By examining the SDL_Event structure within the main loop, we can detect and react to mouse movements, button clicks (including double clicks), and window boundary crossings by the cursor. This forms the basis for implementing mouse controls.

Key Takeaways:

  • The core of input handling is the while(SDL_PollEvent(&E)) loop.
  • Filter events based on E.type (e.g., SDL_MOUSEMOTION, SDL_MOUSEBUTTONDOWN).
  • Access event-specific data using union members (e.g., E.motion, E.button).
  • Mouse position is given by E.motion.x and E.motion.y (y-down).
  • Button identity uses constants like SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT.
  • Button state is checked via E.button.state (SDL_PRESSED, SDL_RELEASED).
  • Double clicks are identified using E.button.clicks.
  • Window enter/leave events are a subtype of SDL_WINDOWEVENT.
  • Dedicated structs like SDL_MouseMotionEvent exist for specific event types.
Next Lesson
Lesson 23 of 129

Rectangles and SDL_Rect

Learn to create, render, and interact with basic rectangles using the SDL_Rect and SDL_Color types.

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

SDL Coordinate System: Why Y-Down?
Why does SDL use a y-down coordinate system instead of y-up?
SDL: Get Mouse Screen Coordinates
Can I get mouse coordinates relative to the screen instead of the window?
SDL_PollEvent() vs SDL_WaitEvent()
What's the difference between SDL_PollEvent() and SDL_WaitEvent()?
SDL Event Timestamp Field
What does the timestamp field in the event structures mean?
SDL Event which Field
What does the which field in the event structures refer to (e.g., multiple mice)?
SDL: Custom Mouse Cursor Appearance
Can I change the mouse cursor's appearance (e.g., to a crosshair)?
SDL Double Click Timing Customization
How does SDL determine the time window for registering a double click? Can I customize this timing?
Retrieve Mouse Position
How do I retrieve the current position of the mouse cursor in SDL?
SDL Mouse Motion Events
What is the difference between SDL_MOUSEMOTION and SDL_MouseMotionEvent?
Detect Double Clicks in SDL
How can I detect double clicks using SDL?
SDL_MouseButtonEvent Structure
What is the SDL_MouseButtonEvent structure used for?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant