SDL School
SDL version Which one?

Use the mouse

This program puts a square outline under the mouse. Each left click stamps a filled square, and a right click clears them. You learn the mouse events, and you store each click in an array.

Move the mouse over the demo, then click.

The full program

main.c
/* Stamp squares with the mouse. The right button clears them. */
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>

#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
#define STAMP_SIZE 32.0f
#define MAX_STAMPS 200

static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_FPoint mouse = { WINDOW_WIDTH / 2.0f, WINDOW_HEIGHT / 2.0f };
static SDL_FPoint stamps[MAX_STAMPS];
static int stamp_count = 0;

/* Make a square with its middle at a point. */
static SDL_FRect square_at(SDL_FPoint point)
{
    SDL_FRect square = {
        point.x - STAMP_SIZE / 2.0f,
        point.y - STAMP_SIZE / 2.0f,
        STAMP_SIZE,
        STAMP_SIZE
    };
    return square;
}

SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("SDL_Init failed: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    if (!SDL_CreateWindowAndRenderer("Use the mouse", WINDOW_WIDTH, WINDOW_HEIGHT, 0,
                                     &window, &renderer)) {
        SDL_Log("SDL_CreateWindowAndRenderer failed: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    SDL_SetRenderVSync(renderer, 1);
    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
    if (event->type == SDL_EVENT_QUIT) {
        return SDL_APP_SUCCESS;
    }

    /* Change window coordinates to renderer coordinates. */
    SDL_ConvertEventToRenderCoordinates(renderer, event);

    if (event->type == SDL_EVENT_MOUSE_MOTION) {
        mouse.x = event->motion.x;
        mouse.y = event->motion.y;
    } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
        if (event->button.button == SDL_BUTTON_LEFT && stamp_count < MAX_STAMPS) {
            stamps[stamp_count].x = event->button.x;
            stamps[stamp_count].y = event->button.y;
            stamp_count++;
        } else if (event->button.button == SDL_BUTTON_RIGHT) {
            stamp_count = 0;
        }
    }
    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void *appstate)
{
    SDL_FRect cursor = square_at(mouse);

    SDL_SetRenderDrawColor(renderer, 24, 26, 32, SDL_ALPHA_OPAQUE);
    SDL_RenderClear(renderer);

    SDL_SetRenderDrawColor(renderer, 255, 157, 92, SDL_ALPHA_OPAQUE);
    for (int i = 0; i < stamp_count; i++) {
        SDL_FRect stamp = square_at(stamps[i]);
        SDL_RenderFillRect(renderer, &stamp);
    }

    SDL_SetRenderDrawColor(renderer, 235, 236, 240, SDL_ALPHA_OPAQUE);
    SDL_RenderRect(renderer, &cursor);

    SDL_RenderPresent(renderer);
    return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
}
main.c
/* Stamp squares with the mouse. The right button clears them. */
#include <SDL.h>
#include <stdbool.h>

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
#define STAMP_SIZE 32.0f
#define MAX_STAMPS 200

static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_FPoint mouse = { WINDOW_WIDTH / 2.0f, WINDOW_HEIGHT / 2.0f };
static SDL_FPoint stamps[MAX_STAMPS];
static int stamp_count = 0;
static bool running = true;

/* Make a square with its middle at a point. */
static SDL_FRect square_at(SDL_FPoint point)
{
    SDL_FRect square = {
        point.x - STAMP_SIZE / 2.0f,
        point.y - STAMP_SIZE / 2.0f,
        STAMP_SIZE,
        STAMP_SIZE
    };
    return square;
}

static void handle_event(const SDL_Event *event)
{
    if (event->type == SDL_QUIT) {
        running = false;
    } else if (event->type == SDL_MOUSEMOTION) {
        mouse.x = (float)event->motion.x;
        mouse.y = (float)event->motion.y;
    } else if (event->type == SDL_MOUSEBUTTONDOWN) {
        if (event->button.button == SDL_BUTTON_LEFT && stamp_count < MAX_STAMPS) {
            stamps[stamp_count].x = (float)event->button.x;
            stamps[stamp_count].y = (float)event->button.y;
            stamp_count++;
        } else if (event->button.button == SDL_BUTTON_RIGHT) {
            stamp_count = 0;
        }
    }
}

static void frame(void)
{
    SDL_Event event;
    SDL_FRect cursor = square_at(mouse);

    while (SDL_PollEvent(&event)) {
        handle_event(&event);
    }

    SDL_SetRenderDrawColor(renderer, 24, 26, 32, SDL_ALPHA_OPAQUE);
    SDL_RenderClear(renderer);

    SDL_SetRenderDrawColor(renderer, 255, 157, 92, SDL_ALPHA_OPAQUE);
    for (int i = 0; i < stamp_count; i++) {
        SDL_FRect stamp = square_at(stamps[i]);
        SDL_RenderFillRectF(renderer, &stamp);
    }

    SDL_SetRenderDrawColor(renderer, 235, 236, 240, SDL_ALPHA_OPAQUE);
    SDL_RenderDrawRectF(renderer, &cursor);

    SDL_RenderPresent(renderer);
}

int main(int argc, char *argv[])
{
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        SDL_Log("SDL_Init failed: %s", SDL_GetError());
        return 1;
    }

    if (SDL_CreateWindowAndRenderer(WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer) != 0) {
        SDL_Log("SDL_CreateWindowAndRenderer failed: %s", SDL_GetError());
        SDL_Quit();
        return 1;
    }

    SDL_SetWindowTitle(window, "Use the mouse");
    SDL_RenderSetVSync(renderer, 1);

#ifdef __EMSCRIPTEN__
    emscripten_set_main_loop(frame, 0, 1);
#else
    while (running) {
        frame();
    }
#endif

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

Mouse events

SDL sends an event for each mouse action. This program uses two kinds:

  • SDL_EVENT_MOUSE_MOTION means that the mouse moved.
  • SDL_EVENT_MOUSE_BUTTON_DOWN means that a button went down.
    if (event->type == SDL_EVENT_MOUSE_MOTION) {
        mouse.x = event->motion.x;
        mouse.y = event->motion.y;

The event holds the position, so the program copies it into mouse. Later, the frame code draws the outline at that position.

  • SDL_MOUSEMOTION means that the mouse moved.
  • SDL_MOUSEBUTTONDOWN means that a button went down.
    } else if (event->type == SDL_MOUSEMOTION) {
        mouse.x = (float)event->motion.x;
        mouse.y = (float)event->motion.y;

The event holds the position as an int, so the program converts it to float and copies it into mouse. Later, the frame code draws the outline at that position.

The event code only records what happened. The drawing happens in the frame code. This is the same split as update and draw in Pong.

Coordinates

    SDL_ConvertEventToRenderCoordinates(renderer, event);

Mouse events use window coordinates. Drawing calls use renderer coordinates. In this program the two are the same, but they differ after you scale the renderer. This call changes the event to renderer coordinates, so your code stays correct later.

Which button

        if (event->button.button == SDL_BUTTON_LEFT && stamp_count < MAX_STAMPS) {
        if (event->button.button == SDL_BUTTON_LEFT && stamp_count < MAX_STAMPS) {

The first button is the part of the event for mouse buttons. The second button is the number of the button. SDL_BUTTON_LEFT and SDL_BUTTON_RIGHT name the two main buttons.

Store the clicks

static SDL_FPoint stamps[MAX_STAMPS];
static int stamp_count = 0;

The array has a fixed size, and stamp_count tells you how many entries are in use. The test stamp_count < MAX_STAMPS protects the array. Without this test, click number 201 writes past the end of the array. C does not stop you from doing this, and the program can crash much later, in a place that looks unrelated.

To clear the stamps, the program sets stamp_count to 0. It does not need to erase the old values, because the loop reads only the entries below the count.

A function that returns a struct

static SDL_FRect square_at(SDL_FPoint point)

C can return a struct by value. This function makes a rectangle with its middle at the mouse position. An SDL_FRect holds the top-left corner, so the function subtracts half of the size.

Try this

  1. Change MAX_STAMPS to 3, then click many times. The fourth click does nothing.
  2. Drag with the left button to draw a line of stamps. Use a variable to remember that the button is down.
  3. Add the event for the mouse wheel to change STAMP_SIZE. You must make it a variable first.

What you learned