create a custom title bar while keeping the native window resize border?

Hello, I want to create a custom title bar and I have a question.

When I remove only the title bar, the window’s resize border also disappears. But I want the title bar to be removed while keeping the native resize border.

My question is: do I need to implement the resize behavior myself from scratch?

Or is there a way to only change the appearance of the title bar while keeping the standard window buttons and native behaviors, similar to apps like VS Code?

Or do I need to build the whole thing manually?

image

As far as I’m aware;
Yes, you need to define new borders. If you want full control of the window, then you are given full control. However, there are helper functions to make this easier.

Here’s an example of setting a new hit-test, see how clicking in the top 20 pixels of the yellow window will move it, otherwise clicking in the right-most 20 pixels will resize it:

#include <SDL3/SDL.h>

SDL_HitTestResult myHitTest(SDL_Window * window, const SDL_Point * click, void * data)
{
	int posx;
	SDL_GetWindowSize(window, &posx, NULL);
	if(click->y < 20)
	{
		return SDL_HITTEST_DRAGGABLE;
	}
	if(click->x > posx - 20)
	{
		return SDL_HITTEST_RESIZE_BOTTOMRIGHT;
	}
	return SDL_HITTEST_NORMAL;
}

int main()
{
	SDL_Init(SDL_INIT_VIDEO);
	SDL_Window * window = SDL_CreateWindow("Main Window", 1000, 1000, SDL_WINDOW_RESIZABLE);
	SDL_Renderer * renderer = SDL_CreateRenderer(window, 0);
	SDL_SetRenderVSync(renderer, 1);

	SDL_Window * window2 = SDL_CreateWindow("Yellow Test", 400, 400, SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS);
	//SDL_SetWindowBordered(window2, true);
	SDL_ShowWindow(window2);
	SDL_Renderer * ren2 = SDL_CreateRenderer(window2, 0);
	SDL_SetWindowPosition(window2, 10, 10);
	SDL_SetWindowHitTest(window2, myHitTest, NULL);
	SDL_FRect titleBorder = {0, 0, 400, 20};
	SDL_FRect resizeBorder = {380, 20, 20, 400};

	SDL_SetRenderDrawBlendMode(ren2, SDL_BLENDMODE_BLEND);

	bool run = true;
	while(run)
	{
		SDL_Event ev;
		while(SDL_PollEvent(&ev))
		{
			switch(ev.type)
			{
				case SDL_EVENT_WINDOW_RESIZED:
					if(ev.window.windowID == SDL_GetWindowID(window2))
					{
						titleBorder.w = ev.window.data1;
						resizeBorder.h = ev.window.data2;
						resizeBorder.x = titleBorder.w - 20;
					}
					break;
				case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
					if(ev.window.windowID == SDL_GetWindowID(window2))
					{
						SDL_Log("No, you can't close this window, close the green one instead");
					}
					else
					{
						run = false;
					}
					break;
				case SDL_EVENT_QUIT:
					run = false;
					break;
			}
		}
		// main window draw commands
		SDL_SetRenderDrawColor(renderer, 0, 100, 80, 255);
		SDL_RenderClear(renderer);
		SDL_RenderPresent(renderer);

		// subwindow draw commands
		SDL_SetRenderDrawColor(ren2, 200, 200, 80, 255);
		SDL_RenderClear(ren2);
		SDL_SetRenderDrawColor(ren2, 20, 20, 255, 55);
		SDL_RenderFillRect(ren2, &resizeBorder);
		SDL_SetRenderDrawColor(ren2, 255, 25, 25, 55);
		SDL_RenderFillRect(ren2, &titleBorder);
		SDL_RenderPresent(ren2);
	}
	SDL_DestroyWindow(window2);
	SDL_Quit();
}

See SDL3/SDL_HitTestResult for other hit-test options.

1 Like

Unfortunately, that’s how Windows works—if you don’t want the standard title bar, the system assumes you don’t need the border either and turns it off (along with the shadows). If you want your window to have a system border but with different dimensions, you’ll need to use functions from the Win32 API. But keep in mind that this is difficult to do, especially if you plan to ensure compatibility with multiple versions of Windows and the themes they support.


But that doesn’t mean your hands are tied—quite the opposite. :wink:

You can easily disable the system-provided borders and then implement your own title bar, borders, client area, system buttons, and so on. This requires writing quite a lot of code, but first of all, it’s not impossible to do, and second, the border can look and behave exactly the way you want. You can have a thick border, a title bar on the left side, more than three system buttons, and so on (I’m just making this up, but the possibilities are endless).

What’s more, the content of these elements can be rendered using the SDL renderer—that is, with the fonts and textures you’re already using in the game. So not only you can customize the layout of window elements to suit your needs, but you can also make them look exactly the way you want and ensure they render very efficiently.


I’m doing exactly the same thing—I disabled the system border and implemented my own. Below is a picture showing the window and a wireframe of its elements. It’s empty right now because I’ve disabled content rendering (I’m still working on the renderer code), so it only shows the terminal content:

At the top is the title bar. To the left of it, that little square is an icon button used to display the system context menu; to the right are five buttons: toggle fills, toggle status bar, minimize, maximize, and close. The large area in the center, which displays the terminal output, is the client area—the area where the game frames will be rendered. If the window’s aspect ratio does not match that of the game frame, there will be empty bars on the sides (or above and below). At the bottom is a status bar that displays various performance counters.

For now, it looks like this and behaves as expected: for example, hit-testing for the title bar and all edges, the system popup under the icon button and RMB on the title bar, mouse capture support for the buttons on the title bar and tooltips for them, etc. However, we can still use the Win32 API to tell DWM that we need its features, such as a 1px window frame, rounded window corners, and a system shadow. But I’ve saved that for later.

If you’d like to decorate your window in a similar way but aren’t sure how, feel free to ask—I’ll try to help.

1 Like