Multiple Displays

Last Updated: May 28th, 2024
Another neat new feature with SDL 2 is the ability to handle multiple displays. Here we'll be making our window jump from display to display.class LWindow
{
public:
//Intializes internals
LWindow();
//Creates window
bool init();
//Handles window events
void handleEvent( SDL_Event& e );
//Focuses on window
void focus();
//Shows windows contents
void render();
//Deallocates internals
void free();
//Window dimensions
int getWidth();
int getHeight();
//Window focii
bool hasMouseFocus();
bool hasKeyboardFocus();
bool isMinimized();
bool isShown();
private:
//Window data
SDL_Window* mWindow;
SDL_Renderer* mRenderer;
int mWindowID;
int mWindowDisplayID;
//Window dimensions
int mWidth;
int mHeight;
//Window focus
bool mMouseFocus;
bool mKeyboardFocus;
bool mFullScreen;
bool mMinimized;
bool mShown;
};
Here is our window from previous tutorials with a window display ID to keep track of which display the window is on.
//Our custom window LWindow gWindow; //Display data int gTotalDisplays = 0; SDL_Rect* gDisplayBounds = NULL;
Our displays all have an integer ID and a rectangle associated with them so we know the position and dimensions of each display on our desktop.
bool LWindow::init()
{
//Create window
mWindow = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE );
if( mWindow != NULL )
{
mMouseFocus = true;
mKeyboardFocus = true;
mWidth = SCREEN_WIDTH;
mHeight = SCREEN_HEIGHT;
//Create renderer for window
mRenderer = SDL_CreateRenderer( mWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC );
if( mRenderer == NULL )
{
printf( "Renderer could not be created! SDL Error: %s\n", SDL_GetError() );
SDL_DestroyWindow( mWindow );
mWindow = NULL;
}
else
{
//Initialize renderer color
SDL_SetRenderDrawColor( mRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
//Grab window identifiers
mWindowID = SDL_GetWindowID( mWindow );
mWindowDisplayID = SDL_GetWindowDisplayIndex( mWindow );
//Flag as opened
mShown = true;
}
}
else
{
printf( "Window could not be created! SDL Error: %s\n", SDL_GetError() );
}
return mWindow != NULL && mRenderer != NULL;
}
Our window creation code is pretty much the same as before only now we made a call to SDL_GetWindowDisplayIndex so we know which display the window was created on.
void LWindow::handleEvent( SDL_Event& e )
{
//Caption update flag
bool updateCaption = false;
//If an event was detected for this window
if( e.type == SDL_WINDOWEVENT && e.window.windowID == mWindowID )
{
switch( e.window.event )
{
//Window moved
case SDL_WINDOWEVENT_MOVED:
mWindowDisplayID = SDL_GetWindowDisplayIndex( mWindow );
updateCaption = true;
break;
//Window appeared
case SDL_WINDOWEVENT_SHOWN:
mShown = true;
break;
//Window disappeared
case SDL_WINDOWEVENT_HIDDEN:
mShown = false;
break;
//Get new dimensions and repaint
case SDL_WINDOWEVENT_SIZE_CHANGED:
mWidth = e.window.data1;
mHeight = e.window.data2;
SDL_RenderPresent( mRenderer );
break;
//Repaint on expose
case SDL_WINDOWEVENT_EXPOSED:
SDL_RenderPresent( mRenderer );
break;
//Mouse enter
case SDL_WINDOWEVENT_ENTER:
mMouseFocus = true;
updateCaption = true;
break;
//Mouse exit
case SDL_WINDOWEVENT_LEAVE:
mMouseFocus = false;
updateCaption = true;
break;
//Keyboard focus gained
case SDL_WINDOWEVENT_FOCUS_GAINED:
mKeyboardFocus = true;
updateCaption = true;
break;
//Keyboard focus lost
case SDL_WINDOWEVENT_FOCUS_LOST:
mKeyboardFocus = false;
updateCaption = true;
break;
//Window minimized
case SDL_WINDOWEVENT_MINIMIZED:
mMinimized = true;
break;
//Window maxized
case SDL_WINDOWEVENT_MAXIMIZED:
mMinimized = false;
break;
//Window restored
case SDL_WINDOWEVENT_RESTORED:
mMinimized = false;
break;
//Hide on close
case SDL_WINDOWEVENT_CLOSE:
SDL_HideWindow( mWindow );
break;
}
}
Here in our window's event handler we handle a SDL_WINDOWEVENT_MOVED event so we can update the display the window is on using SDL_GetWindowDisplayIndex.
else if( e.type == SDL_KEYDOWN )
{
//Display change flag
bool switchDisplay = false;
//Cycle through displays on up/down
switch( e.key.keysym.sym )
{
case SDLK_UP:
++mWindowDisplayID;
switchDisplay = true;
break;
case SDLK_DOWN:
--mWindowDisplayID;
switchDisplay = true;
break;
}
When we press up or down we change the display index to move to the next display.
//Display needs to be updated
if( switchDisplay )
{
//Bound display index
if( mWindowDisplayID < 0 )
{
mWindowDisplayID = gTotalDisplays - 1;
}
else if( mWindowDisplayID >= gTotalDisplays )
{
mWindowDisplayID = 0;
}
//Move window to center of next display
SDL_SetWindowPosition( mWindow, gDisplayBounds[ mWindowDisplayID ].x + ( gDisplayBounds[ mWindowDisplayID ].w - mWidth ) / 2, gDisplayBounds[ mWindowDisplayID ].y + ( gDisplayBounds[ mWindowDisplayID ].h - mHeight ) / 2 );
updateCaption = true;
}
}
//Update window caption with new data
if( updateCaption )
{
std::stringstream caption;
caption << "SDL Tutorial - ID: " << mWindowID << " Display: " << mWindowDisplayID << " MouseFocus:" << ( ( mMouseFocus ) ? "On" : "Off" ) << " KeyboardFocus:" << ( ( mKeyboardFocus ) ? "On" : "Off" );
SDL_SetWindowTitle( mWindow, caption.str().c_str() );
}
}
If we need to move to the next display, we first make sure the display is a valid index by bounding it. We then update the position of the window with
SDL_SetWindowPosition. This call here will center the window in the next display.
bool init()
{
//Initialization flag
bool success = true;
//Initialize SDL
if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
{
printf( "SDL could not initialize! SDL Error: %s\n", SDL_GetError() );
success = false;
}
else
{
//Set texture filtering to linear
if( !SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "1" ) )
{
printf( "Warning: Linear texture filtering not enabled!" );
}
//Get number of displays
gTotalDisplays = SDL_GetNumVideoDisplays();
if( gTotalDisplays < 2 )
{
printf( "Warning: Only one display connected!" );
}
In our initialization function we find out how many displays are connected to the computer using
SDL_GetNumVideoDisplays. If there's only 1 display we output a warning.
//Get bounds of each display
gDisplayBounds = new SDL_Rect[ gTotalDisplays ];
for( int i = 0; i < gTotalDisplays; ++i )
{
SDL_GetDisplayBounds( i, &gDisplayBounds[ i ] );
}
//Create window
if( !gWindow.init() )
{
printf( "Window could not be created!\n" );
success = false;
}
}
return success;
}
Now that we know how many displays are connected, we allocate rectangles for each of them and get the bounds for each one using SDL_GetDisplayBounds. After this we
initialize our window.
//Main loop flag
bool quit = false;
//Event handler
SDL_Event e;
//While application is running
while( !quit )
{
//Handle events on queue
while( SDL_PollEvent( &e ) != 0 )
{
//User requests quit
if( e.type == SDL_QUIT )
{
quit = true;
}
//Handle window events
gWindow.handleEvent( e );
}
//Update window
gWindow.render();
}
Since our code is well encapsulated the main loop hasn't changed since all the changes have happened under the hood.