Window Events and Window IDs

Discover how to monitor and respond to window state changes in SDL3 applications

Ryan McCombe
Updated

Previously, we've seen how we can retrieve aspects of our window's configuration by checking its window flags. However, we don't need to continuously monitor our SDL_Window to understand its state.

For most use cases, monitoring the event loop makes more sense. SDL dispatches a wide variety of events reporting actions performed on our window. In this lesson, we'll explore this in a bit more detail.

Starting Point

This lesson builds on the concepts from the previous lesson. To focus on window events, we will start with a minimal project containing a Window class and a main function with a basic application loop.

Files

src
Select a file to view its content

Window Events in SDL3

A major change from SDL2 to SDL3 is how window events are handled. In SDL2, all window-related events were bundled under a single SDL_WINDOWEVENT type. You had to first check for this general type, and then inspect a sub-event to find out what specifically happened.

In SDL3, this has been simplified. Window events are now top-level events, just like SDL_EVENT_KEY_DOWN or SDL_EVENT_QUIT. This means we can check for them directly in our event loop.

For example, to detect if a window has been minimized, we now check for SDL_EVENT_WINDOW_MINIMIZED directly on the SDL_Event's type member:

while (SDL_PollEvent(&E)) {
  if (E.type == SDL_EVENT_WINDOW_MINIMIZED) {
    // The window was minimized
  }
}

Although the event type is now top-level, the detailed information about the event is still stored in the window member of the SDL_Event union, which is an SDL_WindowEvent struct.

Detecting Any Window Event

Behind the scenes, the type value of an SDL_Event is just an integer, and SDL3 has arranged these integers such that all window events fall within a specific range.

This gives us a way to detect any window event, if we prefer a design that closer matches the SDL2 pattern. To do this, SDL3 provides the SDL_EVENT_WINDOW_FIRST and SDL_EVENT_WINDOW_LAST variables, giving us visibility of where the range of window events begins and ends.

Therefore, we can check if the event was any window even by checking if its type falls within this range:

if (E.type >= SDL_EVENT_WINDOW_FIRST &&
    E.type <= SDL_EVENT_WINDOW_LAST) {
  // This is a window event
}

Let's create a handler function that takes an SDL_WindowEvent and logs a message. In our main loop, we'll check if an event is a window event and, if so, pass E.window to our handler.

src/main.cpp

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"

void HandleWindowEvent(const SDL_WindowEvent& E) {
  std::cout << "Detected Window Event\n";
}

int main(int, char**) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  SDL_Event E;

  bool IsRunning = true;
  while (IsRunning) {
    while (SDL_PollEvent(&E)) {
      if (E.type >= SDL_EVENT_WINDOW_FIRST &&
          E.type <= SDL_EVENT_WINDOW_LAST
        ) {
        HandleWindowEvent(E.window);
      } else if (E.type == SDL_EVENT_QUIT) {
        IsRunning = false;
      }
    }
    GameWindow.Render();
    GameWindow.Update();
  }

  SDL_Quit();
  return 0;
}
Detected Window Event
Detected Window Event
Detected Window Event

Reacting to Specific Window Events

Within our window event handler, we can still check the exact nature of the event with a more specific type check. For example, here's how we can react to a few common window events:

src/main.cpp

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"

void HandleWindowEvent(const SDL_WindowEvent& E) {
  if (E.type == SDL_EVENT_WINDOW_MINIMIZED) {
    std::cout << "Window Minimized\n";
  } else if (E.type == SDL_EVENT_WINDOW_MOVED) {
    std::cout << "Window Moved\n";
  } else if (E.type == SDL_EVENT_WINDOW_MOUSE_ENTER) {
    std::cout << "Cursor Entered the Window\n";
  }
}

int main(int, char**) {/*...*/}
Cursor Entered the Window
Window Moved
Window Minimized

Example: Mouse Entering and Leaving Window

In this example, we'll react to the user's cursor entering and leaving our window by using the SDL_SetWindowTitle() function to change the title based on these events:

src/main.cpp

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"

void HandleWindowEvent(
  const SDL_WindowEvent& E, SDL_Window* Window
) {
  if (E.type == SDL_EVENT_WINDOW_MOUSE_ENTER) {
    SDL_SetWindowTitle(
      Window, "Mouse Inside the Window"
    );
  } else if (E.type == SDL_EVENT_WINDOW_MOUSE_LEAVE) {
    SDL_SetWindowTitle(
      Window, "Mouse Outside the Window"
    );
  }
}

int main(int, char**) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  SDL_Event E;

  bool IsRunning = true;
  while (IsRunning) {
    while (SDL_PollEvent(&E)) {
      if (E.type >= SDL_EVENT_WINDOW_FIRST &&
          E.type <= SDL_EVENT_WINDOW_LAST
      ) {
        HandleWindowEvent(
          E.window, GameWindow.GetRaw()
        );
      } else if (E.type == SDL_EVENT_QUIT) {
        IsRunning = false;
      }
    }
    GameWindow.Render();
    GameWindow.Update();
  }

  SDL_Quit();
  return 0;
}

Window IDs

In the previous example, our HandleWindowEvent() logic requires a pointer to the SDL_Window it needs to change the title of. We passed it as a second argument, but it's also possible to retrieve the SDL_Window directly from the SDL_WindowEvent.

Internally, SDL assigns a unique identifier to each window it creates. This ID is available on the windowID member of the SDL_WindowEvent struct. The type for this ID is SDL_WindowID, which is an alias for a Uint32.

void HandleWindowEvent(const SDL_WindowEvent& E) {
  SDL_WindowID id = E.windowID;
  // ...
}

The windowID variable exists on every event type that relates to a specific window. This includes types like SDL_KeyboardEvent, where the windowID will be the window with input focus, and SDL_MouseMotionEvent, where the ID will be the window the mouse is hovering over.

Getting an SDL_Window* Using SDL_GetWindowFromID()

By passing this window ID to SDL_GetWindowFromID(), we get the corresponding SDL_Window pointer:

SDL_Window* Win{SDL_GetWindowFromID(E.windowID)};

Below, we update our program to use this technique, removing the need to pass our GameWindow pointer to the event handler. This makes the handler more self-contained.

src/main.cpp

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include "Window.h"

void HandleWindowEvent(const SDL_WindowEvent& E) {
  SDL_Window* Window{
    SDL_GetWindowFromID(E.windowID)
  };
  if (!Window) return;

  if (E.type == SDL_EVENT_WINDOW_MOUSE_ENTER) {
    SDL_SetWindowTitle(
      Window, "Mouse Inside the Window"
    );
  } else if (E.type == SDL_EVENT_WINDOW_MOUSE_LEAVE) {
    SDL_SetWindowTitle(
      Window, "Mouse Outside the Window"
    );
  }
}

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

While not strictly necessary for a single-window application, this technique becomes essential in multi-window applications, where you need to know which specific window an event belongs to.

Getting a Window ID Using SDL_GetWindowID()

We can perform this conversion in the opposite direction using SDL_GetWindowID(). We pass it the SDL_Window pointer, and it returns the SDL_WindowID associated with that SDL_Window:

src/main.cpp

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>

int main(int, char**) {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_Window* Window = SDL_CreateWindow(
    "Window ID Example", 600, 300, 0
  );

  SDL_WindowID WindowID{SDL_GetWindowID(Window)};
  std::cout << "The ID of the created window is: "
    << WindowID << '\n';
    
  SDL_Event E;
  bool IsRunning = true;
  while (IsRunning) {
    while (SDL_PollEvent(&E)) {
      if (E.type == SDL_EVENT_QUIT) {
        IsRunning = false;
      }
    }
    GameWindow.Render();
    GameWindow.Update();
  }
  
  SDL_DestroyWindow(Window);
  SDL_Quit();
  return 0;
}
The ID of the created window is: 4

One scenario where this might be useful is when we're using SDL's event system for custom events within our game. SDL provides the SDL_UserEvent type for this purpose, and it includes a windowID member which we can provide if it would be useful to consumers:

Uint32 GAME_OVER{SDL_RegisterEvents(1)};

SDL_Event MyEvent{};
MyEvent.type = GAME_OVER;
MyEvent.user.windowID = SDL_GetWindowID(Window);

SDL_PushEvent(&MyEvent);

Consumers can then retrieve the SDL_Window associated with the event in the usual way:

void HandleGameOverEvent(const SDL_UserEvent& E){
  SDL_Window* Window{
    SDL_GetWindowFromID(E.windowID)
  };
  // ...
}

We covered user events in a dedicated lesson earlier in the course:

Summary

In this lesson, we explored more of SDL3's window management systems, focusing on window events and unique identifiers. Key takeaways:

  • In SDL3, window events like SDL_EVENT_WINDOW_MINIMIZED are top-level events, simplifying event handling.
  • SDL_WindowEvent still provides detailed information about the event via event.window.
  • Window IDs uniquely identify SDL windows, which is crucial for multi-window applications.
  • Use SDL_GetWindowFromID() to retrieve an SDL_Window* from a window ID and SDL_GetWindowID() to find the ID associated with an SDL_Window*.
Next Lesson
Lesson 51 of 52

Managing Window Position

Learn how to control and monitor the position of SDL windows on screen

Have a question about this lesson?
Answers are generated by AI models and may not be accurate