Rectangles and SDL_Rect

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

Ryan McCombe
Published

So far, we've learned how to create a window and fill it with a solid color. While useful, most applications need to draw more specific shapes and images.

One of the most fundamental shapes in 2D graphics is the rectangle. Rectangles are essential for user interfaces (buttons, text boxes), game elements (sprites, collision boxes), and defining areas on the screen (viewports, selection boxes).

SDL provides tools specifically for working with rectangles. We'll learn about the SDL_Rect type for defining position and size, and the SDL_Color type for managing colors independently of screen formats. We'll then combine these to draw rectangles and make them respond to basic mouse interaction.

Starting Point

We'll start with the basic SDL application structure we developed previously. This includes initializing SDL, creating a Window class to manage the SDL_Window and its surface, and running the main event loop.

Creating a Rectangle Class

Let's add a Rectangle class to manage our rectangles. We want our rectangle to render itself to the screen, so we'll add a Render() function that accepts an SDL_Surface pointer. This is the surface that our Rectangle instance will render itself to:

// Rectangle.h
#pragma once
#include <SDL.h>

class Rectangle {
 public:
  void Render(SDL_Surface* Surface) const {
    // ...
  }
};

Over in our main function, let's construct a Rectangle and Render() it at the appropriate time.

As we covered in our double buffering lesson, the rendering process starts with the call to GameWindow.Render() - which fills the window surface with a solid color, and it ends with the call to GameWindow.Update() - which triggers the buffer swap.

All of our other objects, such as our Rectangle, should be rendered between these two steps:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  Rectangle Rect;

  SDL_Event E;
  while (true) {
    while (SDL_PollEvent(&E)) {
      if (E.type == SDL_QUIT) {
        SDL_Quit();
        return 0;
      }
    }

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

  return 0;
}

Rendering the Rectangle

You may remember from our Window class that we can draw a rectangle using SDL_FillRect(), which requires three arguments:

  1. The surface to draw to. This will be the argument provided to our Render() function.
  2. The rectangular area to draw. In our Window class, we're passing a nullptr as this argument, indicating we want the color to fill the entire surface. In this lesson, we'll learn how to provide an argument here, in the form of an SDL_Rect
  3. The color we want to use. Like we did before, we can generate this color using SDL_MapRGB().
// Rectangle.h
#pragma once
#include <SDL.h>

class Rectangle {
 public:
  void Render(SDL_Surface* Surface) const {
    SDL_FillRect(
      Surface,
      
      // We'll provide this later 
      nullptr, 
      
      SDL_MapRGB(Surface->format, 255, 0, 0)
    );
  }
};

The SDL_Rect Type

To represent a rectangle, SDL provides the SDL_Rect type. It is a simple struct with four integer members that store the position and dimensions of the rectangle:

  • The x and y members represent the position of the top-left corner of the rectangle
  • The w member represents the width of the rectangle
  • The h member represents the height of the rectangle

Visually, it looks like this:

Remember, SDL's coordinate system uses the "y-down" convention, so increasing the y value corresponds to moving the rectangle down.

Let's add an SDL_Rect to our Rectangle class. We'll also add it as a constructor parameter:

// Rectangle.h
#pragma once
#include <SDL.h>

class Rectangle {
 public:
  Rectangle(const SDL_Rect& Rect)
  : Rect{Rect} {}
  
void Render(SDL_Surface* Surface) {/*...*/} private: SDL_Rect Rect; };

We'll provide the argument to this constructor in our main.cpp:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  Rectangle Rect{SDL_Rect{50, 50, 50, 50}};

  SDL_Event E;
while (true) {/*...*/} return 0; }

To use this rectangle for rendering, we provide a pointer to it as our second argument to SDL_FillRect():

// Rectangle.h
#pragma once
#include <SDL.h>

class Rectangle {
 public:
  Rectangle(const SDL_Rect& Rect)
  : Rect{Rect} {}
  
  void Render(SDL_Surface* Surface) const {
    SDL_FillRect(
      Surface,
      &Rect,
      SDL_MapRGB(Surface->format, 255, 0, 0)
    );
  }

private:
  SDL_Rect Rect;
};

With these changes, we should now see our red rectangle being rendered to the screen:

The SDL_Color Type

In our Render() function, we're currently generating a red color for our rectangle. However, what if we want to store this color as a member variable, such that we change it from other functions in our Rectangle class?

// Rectangle.h
#pragma once
#include <SDL.h>

class Rectangle {
 public:
  // ...

  void SetColor(/* ??? */) {
    // ???
  }
  
  // ...
};

Previously, we saw how we could store a specific color in a specific pixel format using a 32 bit unsigned integer. This is typically the value returned by a function such as SDL_MapRGB():

Uint32 Red{SDL_MapRGB(Surface->format, 255, 0, 0)};

However, storing our color as a Uint32 in the correct pixel format may not be easy.

In our simple program, we know we're only ever rendering to one surface - the window surface - so, we could design the Rectangle class on that basis. However, this violates the design principle of encapsulation.

Ideally, our Rectangle should be a self-contained component that does not know or care about what is going on elsewhere in our code. In that context, our Rectangle doesn't know what surface it will be rendering to until Render() is invoked. At that point, the SDL_Surface is available as a parameter, and we can retrieve it's pixel format from the format member variable.

A simple option for storing a color in a way that lets us apply the pixel format later would be to use three integers - one for each of the red, green, and blue channels:

// Rectangle.h
#pragma once
#include <SDL.h>

class Rectangle {
 public:
  Rectangle(const SDL_Rect& Rect)
  : Rect{Rect} {}

  void Render(SDL_Surface* Surface) const {
    SDL_FillRect(
      Surface,
      &Rect,
      SDL_MapRGB(
        Surface->format,
        Red, Green, Blue
      )
    );
  }

  void SetColor(int R, int G, int B) {
    Red = R;
    Green = G;
    Blue = B;
  }

private:
  SDL_Rect Rect;
  int Red, Green, Blue;
};

This works, but is a little messy, and it makes writting the getter more difficult. We'd prefer to have a type that can represent a color as a single value. To help with this, SDL provides the SDL_Color type, which is useful for storing a color without locking that color down to a specific pixel format.

SDL_Color is a basic struct with 4 members - r, g, b, and a, representing the red, green, blue, and alpha channels respectively. Each variable is an 8-bit unsigned integer, meaning they can store values from 0 to 255:

SDL_Color Black  {  0,   0,   0, 255};
SDL_Color Gray   {100, 100, 100, 255};
SDL_Color White  {255, 255, 255, 255};
SDL_Color Red    {255,   0,   0, 255};
SDL_Color Green  {  0, 255,   0, 255};
SDL_Color Blue   {  0,   0, 255, 255};
SDL_Color Orange {255, 165,   0, 255};

Let's update our Rectangle to store its color as this type, alongside a getter and setter:

// Rectangle.h
#pragma once
#include <SDL.h>

class Rectangle {
 public:
  Rectangle(const SDL_Rect& Rect)
  : Rect{Rect} {}

  void Render(SDL_Surface* Surface) const {
    SDL_FillRect(
      Surface,
      &Rect,
      SDL_MapRGB(
        Surface->format,
        Color.r, Color.g, Color.b
      )
    );
  }

  void SetColor(const SDL_Color& NewColor) {
    Color = NewColor;
  }

  SDL_Color GetColor() const {
    return Color;
  }

private:
  SDL_Rect Rect;

  // Default to red
  SDL_Color Color{255, 0, 0, 255};
};

Reacting to Mouse Motion Events

Let's give our Rectangle the ability to react to events. To set this up, we'll add a HandleEvent() function to our class:

// Rectangle.h
// ...

class Rectangle {
 public:
  // ...

  void HandleEvent(SDL_Event& E) {
    // We'll implement this next
  }

  // ...
};

We'll also forward events to this function from our event loop:

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

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  Rectangle Rect{SDL_Rect{50, 50, 50, 50}};

  SDL_Event E;
  while (true) {
    while (SDL_PollEvent(&E)) {
      Rect.HandleEvent(E);
      if (E.type == SDL_QUIT) {
        SDL_Quit();
        return 0;
      }
    }

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

  return 0;
}

To detect when the user hovers their mouse over the rectangle, we'll apply the techniques we introduced earlier in the chapter. We'll check for SDL_MOUSEMOTION events and, when they occur, we'll update an isPointerHovering boolean to true if the cursor was moved over our rectangle:

// Rectangle.h
// ...

class Rectangle {
 public:
  // ...
  void HandleEvent(SDL_Event& E) {
    if (E.type == SDL_MOUSEMOTION) {
      isPointerHovering = isWithinRect(
        E.motion.x, E.motion.y
      );
    }
  }
  // ...

private:
  // ...

  bool isPointerHovering{false};
  bool isWithinRect(int x, int y) {
    // We'll implement this next
    return false;
  }
};

Detecting if a Point is within an SDL_Rect

Our isWithinRect() function needs to return true if the x and y coordinates are within the bounds of our Rect variable. There are a few ways of determining this this. One approach is to check if the point is not outside the bounds in all 4 directions:

If the point is not in any of the areas we marked in red, we can deduce that it must be within the rectangle. In code, we can implement the check like this:

// Rectangle.h
// ...

class Rectangle {
 // ...

private:
  // ...
  bool isWithinRect(int x, int y) {
    // Too far left
    if (x < Rect.x) return false;
    // Too far right
    if (x > Rect.x + Rect.w) return false;
    // Too far up
    if (y < Rect.y) return false;
    // Too far down
    if (y > Rect.y + Rect.h) return false;

    // If we got this far, that means the
    // point is within the rectangle
    return true;
  }
};

Changing Color on Hover

Now that our Rectangle is keeping track of whether the user's cursor is hovering over it, other functions in our class can react to it. For example, let's have our rectangle change color when hovered. We'll add a new member variable, getter, and setter:

// Rectangle.h
// ...

class Rectangle {
 public:
  // ...

  void SetHoverColor(const SDL_Color& NewColor) {
    HoverColor = NewColor;
  }

  SDL_Color GetHoverColor() const {
    return HoverColor;
  }

private:
  // ...
  SDL_Color HoverColor{0, 0, 255, 255};
};

Let's update our Render() function to select a color based on the isPointerHovering state. This is an ideal place to use structured binding:

// Rectangle.h
// ...

class Rectangle {
public:
  // ...

  void Render(SDL_Surface* Surface) const {
    auto [r, g, b, a]{
      isPointerHovering ? HoverColor : Color
    };
    
    SDL_FillRect(
      Surface, &Rect,
      SDL_MapRGB(Surface->format, r, g, b)
    );
  }
  // ...
};

Note that, to use structured binding, we need to use all the variables of the SDL_Color type, even though we don't need the a (alpha) channel in this scenario.

Structured Binding

This lesson introduces Structured Binding, a handy tool for unpacking simple data structures

Now, when we hover our rectangle, it should change color from red to blue:

Reacting to Window Events

By default, SDL stops sending us mouse motion events once the pointer leaves our window.

If the rectangle is at the edge of the window, or the mouse is moving quickly, this means we may not be notified that the cursor has left our rectangle through leaving the window entirely.

To address this, we can additionally keep track of SDL_WINDOWEVENT_LEAVE events, using the techniques we covered in the previous lesson:

// Rectangle.h
// ...

class Rectangle {
 public:
  // ...
  void HandleEvent(SDL_Event& E) {
    if (E.type == SDL_MOUSEMOTION) {
      isPointerHovering = isWithinRect(
        E.motion.x, E.motion.y
      );
    } else if (
      E.type == SDL_WINDOWEVENT &&
      E.window.event == SDL_WINDOWEVENT_LEAVE
    ) {
      isPointerHovering = false;
    }
  }
  // ...

};

Reacting to Mouse Click Events

Let's also allow our Rectangle objects to react to mouse click events - that is, events with a type of SDL_MOUSEBUTTONDOWN:

// Rectangle.h
// ...

class Rectangle {
 public:
  // ...

  void HandleEvent(SDL_Event& E) {
    if (E.type == SDL_MOUSEMOTION) {
      isPointerHovering = isWithinRect(
        E.motion.x, E.motion.y
      );
    } else if (
      E.type == SDL_WINDOWEVENT &&
      E.window.event == SDL_WINDOWEVENT_LEAVE
    ) {
      isPointerHovering = false;
    } else if (E.type == SDL_MOUSEBUTTONDOWN) {
      if (E.button.button == SDL_BUTTON_LEFT) {
        std::cout << "A left-click happened "
          "somewhere in the window\n";
      }
    }
  }
  // ...
};

The click events include x and y members, informing us of where the cursor was when the user clicked on our window. We can use this to determine whether they specifically clicked on the Rectangle instance:

// Rectangle.h
// ...

class Rectangle {
 public:
  // ...

  void HandleEvent(SDL_Event& E) {
    if (E.type == SDL_MOUSEMOTION) {
      isPointerHovering = isWithinRect(
        E.motion.x, E.motion.y
      );
    } else if (
      E.type == SDL_WINDOWEVENT &&
      E.window.event == SDL_WINDOWEVENT_LEAVE
    ) {
      isPointerHovering = false;
    } else if (E.type == SDL_MOUSEBUTTONDOWN) {
      if (isWithinRect(E.button.x, E.button.y)
          && E.button.button == SDL_BUTTON_LEFT
      ) {
        std::cout << "A left-click happened "
          "on me!\n";
      }
    }
  }
  // ...
};

Alternatively, we can use the isHovering boolean we added in the previous section:

// Rectangle.h
// ...

class Rectangle {
 public:
  // ...

  void HandleEvent(SDL_Event& E) {
    if (E.type == SDL_MOUSEMOTION) {
      isPointerHovering = isWithinRect(
        E.motion.x, E.motion.y
      );
    } else if (
      E.type == SDL_WINDOWEVENT &&
      E.window.event == SDL_WINDOWEVENT_LEAVE
    ) {
      isPointerHovering = false;
    } else if (E.type == SDL_MOUSEBUTTONDOWN) {
      if (isPointerHovering && 
          E.button.button == SDL_BUTTON_LEFT
      ) {
        std::cout << "A left-click happened "
          "on me!\n";
      }
    }
  }
  // ...
};

Complete Code

Our program now includes a dedicated Rectangle.h file defining our Rectangle class. The main.cpp file creates an instance of this class and integrates its rendering and event handling into the main loop. This setup provides a good starting point for adding more complex features through the rest of this chapter.

Summary

In this lesson, we learned how to define, render, and interact with rectangles in SDL2. We created a Rectangle class encapsulating an SDL_Rect for geometry and an SDL_Color for appearance, allowing us to draw shapes and respond to mouse input.

Key Takeaways:

  • SDL_Rect defines a rectangle using x, y (top-left corner), w (width), and h (height).
  • SDL_Color stores RGBA color values (0-255) independently of pixel formats.
  • SDL_FillRect() draws a filled rectangle onto an SDL_Surface.
  • SDL_MapRGB() converts RGBA values into a surface-specific color format.
  • We can check if a point is inside an SDL_Rect by comparing its coordinates against the rectangle's boundaries.
  • Mouse motion (SDL_MOUSEMOTION) and click (SDL_MOUSEBUTTONDOWN) events provide cursor coordinates.
  • Window events (SDL_WINDOWEVENT_LEAVE) help track when the cursor leaves the window.
Next Lesson
Lesson 24 of 129

Structuring SDL Programs

Discover how to organize SDL components using manager classes, inheritance, and polymorphism for cleaner code.

Questions & Answers

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

Difference Between SDL_Rect and SDL_FRect in SDL2
What's the difference between SDL_Rect and SDL_FRect? When should I use floats?
Drawing Rectangle Outlines in SDL2
How do I draw just the outline of a rectangle instead of filling it?
Using Alpha for Semi-Transparency with SDL_Color in SDL2
Can I make the rectangle semi-transparent using the alpha channel in SDL_Color? How?
Passing SDL_Event by Reference (&E) in SDL2 Event Handling
Why pass the SDL_Event by reference (&E) to HandleEvent()?
Fading Rectangle Color on Hover in SDL2
What if I want the rectangle's color to change gradually when hovered (fade)?
Alternatives to Structured Binding for SDL_Color in Render()
Why did we use structured binding for the color in Render()? What are the alternatives?
Moving a Rectangle in SDL2
How would I move the rectangle around the screen?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant