In this lesson, we'll explore SDL's window management system, learning how to create, configure, and respond to window events.
We'll cover essential concepts like window creation, error handling, and event processing, providing the foundation needed to build robust windowed applications.
SDL_CreateWindow()
As we’ve seen in previous lessons, the main way we create a window using SDL is the SDL_CreateWindow()
function:
SDLWindow* Window{SDL_CreateWindow(
"My Program", // Title
100, // Horizontal Position
200, // Vertical Position
600, // Width
300, // Height
0 // Configuration
)};
This function accepts 6 arguments:
0
, indicating we just want the default configuration.We’ll cover window titles, positioning, and size in detail later in this chapter. In this lesson, we’ll cover the 6th argument - the window flags that control how the window behaves.
The screens that our programs run on can have different pixel densities. A higher-quality screen will have more pixels-per-inch, that is, more pixels in the same physical area when compared to a lower-quality display:
This has implications for how we size and position windows and other elements. For example, if we size our elements on the assumption that the display will have 100 pixels per inch, but it is being run on a display with 200 pixels per inch, the physical size of our element will be much smaller than we intended.
This can impair the usability of our program on newer hardware. By default, operating systems managing that hardware assume our programs have not considered this. They will therefore intervene and reinterpret our requested size to be something more appropriate for the pixel density of the screen it is being displayed on.
The net effect is that we do not necessarily know the units we’re using to size and position windows. It may be in pixels, or it may be in some other unit (sometimes referred to as points or screen coordinates) that will be converted to pixel values appropriate for the screen our content is displayed on.
We cover pixel density in much more detail in the next chapter. This includes preventing the platform from rescaling our content, thereby giving us full control to take advantage of higher quality displays.
SDL_Window
Windows are internally managed by SDL, using a type called SDL_Window
. The SDL_CreateWindow()
function returns a pointer to the SDL_Window
that it created. We should save this pointer, as we’ll need to use it later:
SDLWindow* Window{SDL_CreateWindow(
"My Program",
100, 200, 600, 300, 0
)};
However, we should consider SDL_Window
objects to be read-only.
While it is possible to modify the properties and behavior of windows in our application, this should only be done through specific functions provided by SDL. Direct modification of the SDL_Window structure is not recommended.
We’ll cover most of these functions throughout this chapter.
SDL_DestroyWindow()
The first function used with an SDL_Window*
is SDL_DestroyWindow()
which, as we might guess, deletes the window when we no longer need it.
We should remember to call SDL_DestroyWindow()
when we’re done with any window. Window creation requires SDL to allocate quite a lot of memory so, to prevent memory leaks, we should tell SDL when it’s safe to release those resources:
// Create the Window
SDLWindow* Window{SDL_CreateWindow(
"My Program",
100, 200, 600, 300, 0
)};
// Use the Window
// ...
// Destroy the Window when we're done
SDL_DestroyWindow(Window);
In most programs, our primary window will be open for the entire duration of our program, so this might not seem useful. However, as we’ll see later in this chapter, a program can contain multiple windows.
This can include temporary "utility windows" used to implement UI designs like tooltips and right-click menus.
To manage this, we’ll typically have a custom Window
type that controls an SDL_Window
in the context of our application. This involves creating the SDL_Window
in its constructor, and destroying it in the destructor.
We don’t want objects of this type to be copyable, so we’ll also delete the copy constructor and copy assignment operator:
// Window.h
#pragma once
#include <SDL.h>
class Window {
public:
Window() {
SDLWindow = SDL_CreateWindow(
"My Program",
100, 200, 600, 300, 0
);
}
~Window() { SDL_DestroyWindow(SDLWindow); }
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
SDL_Window* SDLWindow;
};
The SDL_CreateWindow()
function can fail. When this happens, it returns a nullptr
, which we can test for using an if
statement. We can also call SDL_GetError()
to receive an explanation of why window creation failed.
Below, we attempt to create a window using Metal (an API used on Apple systems) on a platform that doesn’t support it:
// Window.h
#pragma once
#include <SDL.h>
class Window {
public:
Window() {
SDLWindow = SDL_CreateWindow(
"My Program",
100, 200, 600, 300,
SDL_WINDOW_METAL
);
if (!SDLWindow) {
std::cout << SDL_GetError();
}
}
~Window() { SDL_DestroyWindow(SDLWindow); }
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
SDL_Window* SDLWindow;
};
// main.cpp
#include <SDL.h>
#include "Window.h"
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Quit();
return 0;
}
Metal support is either not configured in SDL or not available in current SDL video driver (windows) or platform
SDL_WindowFlags
The 6th argument to SDL_CreateWindow()
controlling our window configuration has a type of SDL_WindowFlags
. This type is a bit set, where each individual bit in the type corresponds to whether a specific setting should be enabled or disabled.
A value of 0
corresponds to no flags being enabled:
SDL_WindowFlags Flags{0};
SDL_CreateWindow(
"My Program",
100, 200, 600, 300,
Flags
);
SDL provides a collection of bit masks, helping us select specific bits within the SDL_WindowFlags
bit set. We’ll cover the available window flags throughout the rest of this chapter, but some examples include SDL_WINDOW_FULLSCREEN
, SDL_WINDOW_BORDERLESS
, and SDL_WINDOW_RESIZABLE
:
SDL_WindowFlags A{SDL_WINDOW_FULLSCREEN};
SDL_WindowFlags B{SDL_WINDOW_BORDERLESS};
SDL_WindowFlags C{SDL_WINDOW_RESIZABLE};
One of the benefits of a bit set is that we can easily select multiple flags at once using the |
operator. Below, we create a set of flags that will make the window both borderless and resizable:
SDL_WindowFlags Flags{
SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE
};
Note that the bitwise OR operator uses a single vertical bar - |
. It is not the same as the more common logical OR operator, which uses two: ||
.
We cover bitwise operators and bit flags in much more detail in our introductory course:
SDL_GetWindowFlags()
When our program runs, the state of our window can be changed by the code we write or actions performed by the user. For example, whilst we may configure our window to be initially maximized, the user may later resize it.
SDL keeps the SDL_WindowFlags
associated with a window up to date through these interactions. We can get the current state of a window’s flags by passing the SDL_Window
pointer to SDL_GetWindowFlags()
:
SDL_Window* Window{SDL_CreateWindow(
"My Program",
100, 200, 600, 300,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED
)};
SDL_WindowFlags Flags{
SDL_GetWindowFlags(Window)
};
We can examine the state of a specific flag by using the same masks we introduced earlier. To test if a flag is enabled, we use the bitwise AND operator, &
.
This will return 0
if the flag is disabled, and a non-zero number otherwise:
SDL_Window* Window{SDL_CreateWindow(
"My Program",
100, 200, 600, 300,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED
)};
SDL_WindowFlags Flags{
SDL_GetWindowFlags(Window)
};
bool isMaximized{Flags & SDL_WINDOW_MAXIMIZED != 0};
Let’s update our Window
class to add some of these capabilities. For example, we’ll update our constructor to accept an SDL_WindowFlags
argument and a public method that can be used to determine if our window is currently maximized:
// Window.h
#pragma once
#include <SDL.h>
class Window {
public:
Window(SDL_WindowFlags Flags = 0) {
SDLWindow = SDL_CreateWindow(
"My Program",
100, 200, 600, 300,
Flags
);
if (!SDLWindow) {
std::cout << SDL_GetError();
}
}
bool GetIsMaximized() {
return SDL_GetWindowFlags(SDLWindow) & SDL_WINDOW_MAXIMIZED;
}
~Window() { SDL_DestroyWindow(SDLWindow); }
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
SDL_Window* SDLWindow;
};
In this lesson, we've explored the fundamentals of window management in SDL. We've learned how to create windows, handle errors, configure window behavior using flags, and respond to window events.
Key takeaways:
SDL_CreateWindow()
with customizable position, size, and behaviorSDL_GetError()
to understand whySDL_DestroyWindow()
SDL_WindowFlags
control window behavior through bitwise operationsSDL_GetWindowFlags()
and event handlingExplore window creation, configuration, and event handling using SDL's windowing system
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games