Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I made a Custom Windows Titlebar in ImGui #6951

Closed
nyaruku opened this issue Oct 22, 2023 · 3 comments
Closed

I made a Custom Windows Titlebar in ImGui #6951

nyaruku opened this issue Oct 22, 2023 · 3 comments
Labels

Comments

@nyaruku
Copy link

nyaruku commented Oct 22, 2023

I would like to use ImGUI as GUI for normal desktop applications, but dont really like the Windows default Titlebar and so made my own:

2023-10-22.21-36-21.mp4

ImGUI: v1.89.9
Render: DirectX 9 (something that old PC still support, as I think OpenGL might not be supported by some old hardware).
(but as most code is Windows API dependet, this should also work on other Renders)

Includes:

#include <string>
#include <Windows.h>
#include <Dwmapi.h>
#include <windowsx.h>

Code:

// Create application window
ImGui_ImplWin32_EnableDpiAwareness();
WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr };
::RegisterClassExW(&wc);
HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"osu! Tracker", WS_BORDER | WS_THICKFRAME, default_pos_x, default_pos_y, default_width, default_height, nullptr, nullptr, wc.hInstance, nullptr);
SetWindowLong(hwnd, GWL_STYLE, WS_BORDER | WS_THICKFRAME);
ShowWindow(hwnd, SW_SHOW); //display window
	// Main loop
	bool done = false;

	bool titlebar_area = false;
	bool mouse_hold = false;
	bool dragging_window = false;

	int init_mouse_x;
	int init_mouse_y;

	int temp_window_x = default_pos_x;
	int temp_window_y = default_pos_y;
	
	while (!done)
	{
		// Poll and handle messages (inputs, window resize, etc.)
		// See the WndProc() function below for our to dispatch events to the Win32 backend.
		MSG msg;
		while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE))
		{
			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
			if (msg.message == WM_QUIT)
				done = true;
		}
		if (done)
			break;

		// Handle window resize (we don't resize directly in the WM_SIZE handler)
		if (g_ResizeWidth != 0 && g_ResizeHeight != 0)
		{
			g_d3dpp.BackBufferWidth = g_ResizeWidth;
			g_d3dpp.BackBufferHeight = g_ResizeHeight;
			g_ResizeWidth = g_ResizeHeight = 0;
			ResetDevice();
		}

		//  Start the Dear ImGui frame
		//	ImGui_ImplDX9_NewFrame();
		ImGui_ImplWin32_NewFrame();
		ImGui::NewFrame();

		static ImGuiWindowFlags titlebar_flags =
			ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse
			| ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove
			| ImGuiWindowFlags_NoScrollbar
			| ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollWithMouse;

		const ImGuiViewport* viewport = ImGui::GetMainViewport();

		// check if mouse cursor is inside the titlebar area & compensate 7px padding: is used for the window border resize
		if ((io.MousePos.x >= 7 && io.MousePos.x <= (viewport->WorkSize.x - 7)) && (io.MousePos.y >= 7 && io.MousePos.y <= titlebar_height)) {
			titlebar_area = true;

			if (io.MouseDownDuration[5] == 0.000000f) {
				// started dragging
				dragging_window = true;
				init_mouse_x = io.MousePos.x;
				init_mouse_y = io.MousePos.y;
			}

			if (io.MouseDownDuration[5] == -1.000000f) {
				// stopped dragging
				dragging_window = false;
				
			}
		}
		else {
			titlebar_area = false;
			//dragging_window = false;
		}

		if (dragging_window) {
			temp_window_x += io.MousePos.x - init_mouse_x;
			temp_window_y += io.MousePos.y - init_mouse_y;
			SetWindowPos(hwnd, NULL, temp_window_x, temp_window_y, viewport->WorkSize.x, viewport->WorkSize.y, NULL);
			
		}

		ImGui::SetNextWindowPos(viewport->WorkPos);
		ImGui::SetNextWindowSize(ImVec2{ viewport->WorkSize.x, titlebar_height });

		ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
		ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2{ 0.0f, 0.0f });

		// Properly center the content accoring to the titlebar height
		ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{ titlebar_height / 3, titlebar_height / 3 });
		ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4{ 0.01f, 0.01f, 0.01f, 1.0f });

		ImGui::Begin("window-frame-titlebar", nullptr, titlebar_flags);
		ImGui::PopStyleVar(3);
		ImGui::PopStyleColor(1);
		//ImGui::Image(img_icon, ImVec2{ 1.0f, 1.0f });
		ImGui::SetWindowFontScale(1.0f);

		ImGui::TextColored(ImVec4{ 1.0f,1.0f,1.0f,1.0f }, "Application Title");

		ImGui::End();

		ImGui::Begin("DEBUG");
		ImGui::Text("Mouse Position:");
		ImGui::Text(("X: " + std::to_string(io.MousePos.x)).c_str());
		ImGui::Text(("Y: " + std::to_string(io.MousePos.y)).c_str());
		ImGui::Text(("Window Width: " + std::to_string(viewport->WorkSize.x)).c_str());
		ImGui::Text(("Window Height: " + std::to_string(viewport->WorkSize.y)).c_str());
		ImGui::Text(("Mouse over titlebar?: " + boolToString(titlebar_area)).c_str()); // BoolToString is my own made function
		ImGui::Text(("Mouse button down?: " + std::to_string(io.MouseDownDuration[5])).c_str());
		ImGui::Text(("Dragging Window?: " + boolToString(dragging_window)).c_str());
		ImGui::End();

		// Rendering
		ImGui::EndFrame();
		g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
		g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
		g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE);
		D3DCOLOR clear_col_dx = D3DCOLOR_RGBA((int)(clear_color.x * clear_color.w * 20.0f), (int)(clear_color.y * clear_color.w * 20.0f), (int)(clear_color.z * clear_color.w * 20.0f), (int)(clear_color.w * 255.0f));
		g_pd3dDevice->Clear(0, nullptr, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, clear_col_dx, 0.5f, 0);
		if (g_pd3dDevice->BeginScene() >= 0)
		{
			ImGui::Render();
			ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
			g_pd3dDevice->EndScene();
		}
		HRESULT result = g_pd3dDevice->Present(nullptr, nullptr, nullptr, nullptr);

		// Handle loss of D3D9 device
		if (result == D3DERR_DEVICELOST && g_pd3dDevice->TestCooperativeLevel() == D3DERR_DEVICENOTRESET)
			ResetDevice();
	}

	ImGui_ImplDX9_Shutdown();
	ImGui_ImplWin32_Shutdown();
	ImGui::DestroyContext();

	CleanupDeviceD3D();
	::DestroyWindow(hwnd);
	::UnregisterClassW(wc.lpszClassName, wc.hInstance);

	return 0;
}

Modified WndProc Function form Dx9 Example to Achieve this
Should work for Windows Vista/7/8/10

LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	static RECT border_thickness;
	if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
		return true;

	switch (msg)
	{
	
	case WM_CREATE:
	{
		//find border thickness
		SetRectEmpty(&border_thickness);
		if (GetWindowLongPtr(hWnd, GWL_STYLE) & WS_THICKFRAME)
		{
			AdjustWindowRectEx(&border_thickness, GetWindowLongPtr(hWnd, GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
			border_thickness.left *= -1;
			border_thickness.top *= -1;
		}
		else if (GetWindowLongPtr(hWnd, GWL_STYLE) & WS_BORDER)
		{
			SetRect(&border_thickness, 1, 1, 1, 1);
		}

		MARGINS margins = { 0 };
		DwmExtendFrameIntoClientArea(hWnd, &margins);
		SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
		break;
	}
	
	case WM_NCACTIVATE:
		return 0;

	case WM_SIZE:
		if (wParam == SIZE_MINIMIZED)
			return 0;
		g_ResizeWidth = (UINT)LOWORD(lParam); // Queue resize
		g_ResizeHeight = (UINT)HIWORD(lParam);
		return 0;
	case WM_NCCALCSIZE:
		if (lParam)
			return 0;

	case WM_NCHITTEST:
	{
		POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
		ScreenToClient(hWnd, &pt);
		RECT rc;
		GetClientRect(hWnd, &rc);
		enum { left = 1, top = 2, right = 4, bottom = 8 };
		int hit = 0;
		if (pt.x < border_thickness.left) hit |= left;
		if (pt.x > rc.right - border_thickness.right) hit |= right;
		if (pt.y < border_thickness.top) hit |= top;
		if (pt.y > rc.bottom - border_thickness.bottom) hit |= bottom;

		if (hit & top && hit & left) return HTTOPLEFT;
		if (hit & top && hit & right) return HTTOPRIGHT;
		if (hit & bottom && hit & left) return HTBOTTOMLEFT;
		if (hit & bottom && hit & right) return HTBOTTOMRIGHT;
		if (hit & left) return HTLEFT;
		if (hit & top) return HTTOP;
		if (hit & right) return HTRIGHT;
		if (hit & bottom) return HTBOTTOM;

		return HTCLIENT;
	}
	case WM_SYSCOMMAND:
		if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu
			return 0;
		break;
	
	case WM_DESTROY:
		::PostQuitMessage(0);
		return 0;
	}
	return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}

Maybe this can be added into IMGUI, but should maybe be improved/optimized...

@sakiodre
Copy link
Contributor

Hi, please post this in the Gallery instead

@ocornut ocornut changed the title I made a Custom Titlebar in ImGUI I made a Custom Windows Titlebar in ImGui Oct 26, 2023
@ocornut
Copy link
Owner

ocornut commented Oct 26, 2023

Hello,

Thanks for sharing your code.

I think this align with the general sentiment of #3680 #3350, and generally adding more imgui->platform forwarding controls such as the ability to move a platform window but also minimize/maximize (#3486). Also see e.g. #6162 which has code.

I'm all for our multi-viewport system and backends supporting ways to do this, but we'd need sensible cross-platform solution which are more work.

I think I will work on a Windows only proof of concept for this so I have more understanding of what dear imgui could provide and so we could share correct code.

As per your code, I can see a few issues:

  • hardcoded padding coordinates which are likely to be incorrect
  • MouseDownDuration[5] is out of bound.
  • the dragging shown in your video seems snappy/broken.

@ocornut ocornut closed this as completed Oct 26, 2023
@kraptor23
Copy link

ImGui is an incredible framework for our cross platform GUI. We are also realizing that each OS user expects the windows (multi viewports) to behave more like a native window, implementing minimize etc from the title bar. Has there been any progress on this front? We'd be happy to sponsor efforts here if it's possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants