diff --git a/examples/directx11_example/main.cpp b/examples/directx11_example/main.cpp index c52fe00a8..356798e59 100644 --- a/examples/directx11_example/main.cpp +++ b/examples/directx11_example/main.cpp @@ -117,10 +117,12 @@ int main(int, char**) // Setup ImGui binding ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); (void)io; + ImGuiIO& io = ImGui::GetIO(); + io.NavFlags |= ImGuiNavFlags_EnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_MultiViewports; + ImGui_ImplWin32_Init(hwnd); ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext); - //io.NavFlags |= ImGuiNavFlags_EnableKeyboard; // Enable Keyboard Controls // Setup style ImGui::StyleColorsDark(); @@ -206,6 +208,9 @@ int main(int, char**) ImGui::Render(); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindows(); + g_pSwapChain->Present(1, 0); // Present with vsync //g_pSwapChain->Present(0, 0); // Present without vsync } diff --git a/examples/imgui_impl_dx11.cpp b/examples/imgui_impl_dx11.cpp index 44e5737a2..ed4736d02 100644 --- a/examples/imgui_impl_dx11.cpp +++ b/examples/imgui_impl_dx11.cpp @@ -11,6 +11,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2018-XX-XX: Platform: Added support for multiple windows via the ImGuiRendererInterface // 2018-XX-XX: DirectX11: Offset projection matrix and clipping rectangle by io.DisplayPos (which will be non-zero for multi-viewport applications). // 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX11_RenderDrawData() in the .h file so you can call it yourself. // 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. @@ -26,6 +27,7 @@ // DirectX data static ID3D11Device* g_pd3dDevice = NULL; static ID3D11DeviceContext* g_pd3dDeviceContext = NULL; +static IDXGIFactory1* g_pFactory = NULL; static ID3D11Buffer* g_pVB = NULL; static ID3D11Buffer* g_pIB = NULL; static ID3D10Blob * g_pVertexShaderBlob = NULL; @@ -46,6 +48,10 @@ struct VERTEX_CONSTANT_BUFFER float mvp[4][4]; }; +// Forward Declarations +static void ImGui_ImplDX11_InitPlatformInterface(); +static void ImGui_ImplDX11_ShutdownPlatformInterface(); + // Render function // (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) @@ -466,13 +472,29 @@ void ImGui_ImplDX11_InvalidateDeviceObjects() bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context) { + // Get factory from device + IDXGIDevice* pDXGIDevice = NULL; + IDXGIAdapter* pDXGIAdapter = NULL; + IDXGIFactory1* pFactory = NULL; + if (device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice)) != S_OK) + return false; + if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) != S_OK) + return false; + if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) != S_OK) + return false; + + ImGuiIO& io = ImGui::GetIO(); g_pd3dDevice = device; g_pd3dDeviceContext = device_context; + g_pFactory = pFactory; + if (io.ConfigFlags & ImGuiConfigFlags_MultiViewports) + ImGui_ImplDX11_InitPlatformInterface(); return true; } void ImGui_ImplDX11_Shutdown() { + ImGui_ImplDX11_ShutdownPlatformInterface(); ImGui_ImplDX11_InvalidateDeviceObjects(); g_pd3dDevice = NULL; g_pd3dDeviceContext = NULL; @@ -483,3 +505,121 @@ void ImGui_ImplDX11_NewFrame() if (!g_pFontSampler) ImGui_ImplDX11_CreateDeviceObjects(); } + +// -------------------------------------------------------------------------------------------------------- +// Platform Windows +// -------------------------------------------------------------------------------------------------------- + +#include "imgui_internal.h" // ImGuiViewport + +struct ImGuiPlatformDataDx11 +{ + IDXGISwapChain* SwapChain; + ID3D11RenderTargetView* RTView; + + ImGuiPlatformDataDx11() { SwapChain = NULL; RTView = NULL; } + ~ImGuiPlatformDataDx11() { IM_ASSERT(SwapChain == NULL && RTView == NULL); } +}; + +static void ImGui_ImplDX11_CreateViewport(ImGuiViewport* viewport) +{ + ImGuiPlatformDataDx11* data = IM_NEW(ImGuiPlatformDataDx11)(); + viewport->RendererUserData = data; + + // FIXME-PLATFORM + HWND hwnd = (HWND)viewport->PlatformHandle; + IM_ASSERT(hwnd != 0); + + // Create swap chain + DXGI_SWAP_CHAIN_DESC sd; + ZeroMemory(&sd, sizeof(sd)); + sd.BufferDesc.Width = (UINT)viewport->Size.x; + sd.BufferDesc.Height = (UINT)viewport->Size.y; + sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + sd.SampleDesc.Count = 1; + sd.SampleDesc.Quality = 0; + sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.BufferCount = 1; + sd.OutputWindow = hwnd; + sd.Windowed = TRUE; + sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + sd.Flags = 0; + + IM_ASSERT(data->SwapChain == NULL && data->RTView == NULL); + g_pFactory->CreateSwapChain(g_pd3dDevice, &sd, &data->SwapChain); + + // Create the render target + if (data->SwapChain) + { + ID3D11Texture2D* pBackBuffer; + data->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); + g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &data->RTView); + pBackBuffer->Release(); + } +} + +static void ImGui_ImplDX11_DestroyViewport(ImGuiViewport* viewport) +{ + if (ImGuiPlatformDataDx11* data = (ImGuiPlatformDataDx11*)viewport->RendererUserData) + { + if (data->SwapChain) + data->SwapChain->Release(); + data->SwapChain = NULL; + if (data->RTView) + data->RTView->Release(); + data->RTView = NULL; + IM_DELETE(data); + } + viewport->RendererUserData = NULL; +} + +static void ImGui_ImplDX11_ResizeViewport(ImGuiViewport* viewport, int w, int h) +{ + ImGuiPlatformDataDx11* data = (ImGuiPlatformDataDx11*)viewport->RendererUserData; + if (data->RTView) + { + data->RTView->Release(); + data->RTView = NULL; + } + if (data->SwapChain) + { + ID3D11Texture2D* pBackBuffer = NULL; + data->SwapChain->ResizeBuffers(0, w, h, DXGI_FORMAT_UNKNOWN, 0); + data->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); + g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &data->RTView); + pBackBuffer->Release(); + } +} + +static void ImGui_ImplDX11_RenderViewport(ImGuiViewport* viewport) +{ + ImGuiPlatformDataDx11* data = (ImGuiPlatformDataDx11*)viewport->RendererUserData; + ImVec4 clear_color = ImGui::GetStyle().Colors[ImGuiCol_WindowBg]; // FIXME-PLATFORM + clear_color.w = 1.0f; + g_pd3dDeviceContext->OMSetRenderTargets(1, &data->RTView, NULL); + g_pd3dDeviceContext->ClearRenderTargetView(data->RTView, (float*)&clear_color); + ImGui_ImplDX11_RenderDrawData(&viewport->DrawData); +} + +static void ImGui_ImplDX11_SwapBuffers(ImGuiViewport* viewport) +{ + ImGuiPlatformDataDx11* data = (ImGuiPlatformDataDx11*)viewport->RendererUserData; + data->SwapChain->Present(0, 0); // Present without vsync +} + +void ImGui_ImplDX11_InitPlatformInterface() +{ + ImGuiIO& io = ImGui::GetIO(); + io.RendererInterface.CreateViewport = ImGui_ImplDX11_CreateViewport; + io.RendererInterface.DestroyViewport = ImGui_ImplDX11_DestroyViewport; + io.RendererInterface.ResizeViewport = ImGui_ImplDX11_ResizeViewport; + io.RendererInterface.RenderViewport = ImGui_ImplDX11_RenderViewport; + io.RendererInterface.SwapBuffers = ImGui_ImplDX11_SwapBuffers; +} + +void ImGui_ImplDX11_ShutdownPlatformInterface() +{ + ImGuiIO& io = ImGui::GetIO(); + memset(&io.RendererInterface, 0, sizeof(io.RendererInterface)); +} + diff --git a/examples/imgui_impl_win32.cpp b/examples/imgui_impl_win32.cpp index f538590a7..331b67ae0 100644 --- a/examples/imgui_impl_win32.cpp +++ b/examples/imgui_impl_win32.cpp @@ -5,8 +5,12 @@ #include "imgui_impl_win32.h" #define WIN32_LEAN_AND_MEAN #include +#include + +#include "imgui_internal.h" // FIXME-PLATFORM // CHANGELOG +// 2018-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformInterface // 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value and WM_SETCURSOR message handling). // 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. // 2018-02-06: Inputs: Honoring the io.WantMoveMouse by repositioning the mouse by using navigation and ImGuiNavFlags_MoveMouse is set. @@ -24,6 +28,10 @@ static INT64 g_Time = 0; static INT64 g_TicksPerSecond = 0; static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_Count_; +// Forward Declarations +static void ImGui_ImplWin32_InitPlatformInterface(); +static void ImGui_ImplWin32_ShutdownPlatformInterface(); + // Functions bool ImGui_ImplWin32_Init(void* hwnd) { @@ -57,11 +65,21 @@ bool ImGui_ImplWin32_Init(void* hwnd) io.KeyMap[ImGuiKey_Y] = 'Y'; io.KeyMap[ImGuiKey_Z] = 'Z'; - io.ImeWindowHandle = g_hWnd; return true; + io.ImeWindowHandle = g_hWnd; + + // Our mouse update function expect PlatformHandle to be filled for the main viewport + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + main_viewport->PlatformHandle = (void*)g_hWnd; + + if (io.ConfigFlags & ImGuiConfigFlags_MultiViewports) + ImGui_ImplWin32_InitPlatformInterface(); + + return true; } void ImGui_ImplWin32_Shutdown() { + ImGui_ImplWin32_ShutdownPlatformInterface(); g_hWnd = (HWND)0; } @@ -92,6 +110,42 @@ static void ImGui_ImplWin32_UpdateMouseCursor() } } +// This code supports multiple OS Windows mapped into different ImGui viewports, +// So it is a little more complicated than your typical binding code (which only needs to set io.MousePos in your WM_MOUSEMOVE handler) +// This is what imgui needs from the back-end to support multiple windows: +// - io.MousePos = mouse position (e.g. io.MousePos == viewport->Pos when we are on the upper-left of our viewport) +// - io.MousePosViewport = viewport which mouse position is based from (generally the focused/active/capturing viewport) +// - io.MouseHoveredWindow = viewport which mouse is hovering, **regardless of it being the active/focused window**, **regardless of another window holding mouse captured**. [Optional] +// This function overwrite the value of io.MousePos normally updated by the WM_MOUSEMOVE handler. +// We keep the WM_MOUSEMOVE handling code so that WndProc function can be copied as-in in applications which do not need multiple OS windows support. +static void ImGui_ImplWin32_UpdateMousePos() +{ + ImGuiIO& io = ImGui::GetIO(); + io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + io.MousePosViewport = 0; + io.MouseHoveredViewport = 0; + + POINT pos; + if (!::GetCursorPos(&pos)) + return; + + // Our back-end can tell which window is under the mouse cursor (not every back-end can), so pass that info to imgui + io.ConfigFlags |= ImGuiConfigFlags_PlatformHasMouseHoveredViewport; + HWND hovered_hwnd = ::WindowFromPoint(pos); + if (hovered_hwnd) + if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle((void*)hovered_hwnd)) + io.MouseHoveredViewport = viewport->ID; + + // Convert mouse from screen position to window client position + HWND focused_hwnd = ::GetActiveWindow(); + if (focused_hwnd != 0 && ::ScreenToClient(focused_hwnd, &pos)) + if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle((void*)focused_hwnd)) + { + io.MousePos = ImVec2(viewport->Pos.x + (float)pos.x, viewport->Pos.y + (float)pos.y); + io.MousePosViewport = viewport->ID; + } +} + void ImGui_ImplWin32_NewFrame() { ImGuiIO& io = ImGui::GetIO(); @@ -133,6 +187,8 @@ void ImGui_ImplWin32_NewFrame() ImGui_ImplWin32_UpdateMouseCursor(); } + ImGui_ImplWin32_UpdateMousePos(); + // Start the frame. This call will update the io.WantCaptureMouse, io.WantCaptureKeyboard flag that you can use to dispatch inputs (or not) to your application. ImGui::NewFrame(); } @@ -213,3 +269,199 @@ IMGUI_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wPa } return 0; } + +// -------------------------------------------------------------------------------------------------------- +// Platform Windows +// -------------------------------------------------------------------------------------------------------- + +struct ImGuiPlatformDataWin32 +{ + HWND Hwnd; + bool ExternalResize; + DWORD DwStyle; + DWORD DwExStyle; + + ImGuiPlatformDataWin32() { Hwnd = NULL; ExternalResize = false; DwStyle = DwExStyle = 0; } + ~ImGuiPlatformDataWin32() { IM_ASSERT(Hwnd == NULL); } +}; + +static void ImGui_ImplWin32_CreateViewport(ImGuiViewport* viewport) +{ + ImGuiPlatformDataWin32* data = IM_NEW(ImGuiPlatformDataWin32)(); + viewport->PlatformUserData = data; + + if (viewport->Flags & ImGuiViewportFlags_NoDecoration) + { + data->DwStyle = WS_POPUP; + data->DwExStyle = 0; + } + else + { + data->DwStyle = WS_OVERLAPPEDWINDOW; + data->DwExStyle = WS_EX_TOOLWINDOW; + } + + // Create window + RECT rect = { (LONG)viewport->PlatformOsDesktopPos.x, (LONG)viewport->PlatformOsDesktopPos.y, (LONG)(viewport->PlatformOsDesktopPos.x + viewport->Size.x), (LONG)(viewport->PlatformOsDesktopPos.y + viewport->Size.y) }; + ::AdjustWindowRectEx(&rect, data->DwStyle, FALSE, data->DwExStyle); + data->ExternalResize = true; + data->Hwnd = ::CreateWindowExA( + data->DwExStyle, "ImGui Platform", "No Title Yet", data->DwStyle, // Style, class name, window name + rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, // Window area + g_hWnd, NULL, ::GetModuleHandle(NULL), NULL); // Parent window, Menu, Instance, Param + data->ExternalResize = false; + viewport->PlatformHandle = data->Hwnd; +} + +static void ImGui_ImplWin32_DestroyViewport(ImGuiViewport* viewport) +{ + if (ImGuiPlatformDataWin32* data = (ImGuiPlatformDataWin32*)viewport->PlatformUserData) + { + if (::GetCapture() == data->Hwnd) + { + // Transfer capture so if we started dragging from a window that later disappears, we'll still release the MOUSEUP event. + ::ReleaseCapture(); + ::SetCapture(g_hWnd); + } + if (data->Hwnd) + ::DestroyWindow(data->Hwnd); + data->Hwnd = NULL; + IM_DELETE(data); + } + viewport->PlatformUserData = viewport->PlatformHandle = NULL; +} + +static void ImGui_ImplWin32_ShowWindow(ImGuiViewport* viewport) +{ + ImGuiPlatformDataWin32* data = (ImGuiPlatformDataWin32*)viewport->PlatformUserData; + IM_ASSERT(data->Hwnd != 0); + data->ExternalResize = true; + if (viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing) + ::ShowWindow(data->Hwnd, SW_SHOWNA); + else + ::ShowWindow(data->Hwnd, SW_SHOW); + data->ExternalResize = false; +} + +static ImVec2 ImGui_ImplWin32_GetWindowPos(ImGuiViewport* viewport) +{ + ImGuiPlatformDataWin32* data = (ImGuiPlatformDataWin32*)viewport->PlatformUserData; + IM_ASSERT(data->Hwnd != 0); + POINT pos = { 0, 0 }; + ::ClientToScreen(data->Hwnd, &pos); + return ImVec2((float)pos.x, (float)pos.y); +} + +static void ImGui_ImplWin32_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos) +{ + ImGuiPlatformDataWin32* data = (ImGuiPlatformDataWin32*)viewport->PlatformUserData; + IM_ASSERT(data->Hwnd != 0); + RECT rect = { (LONG)pos.x, (LONG)pos.y, (LONG)pos.x, (LONG)pos.y }; + ::AdjustWindowRectEx(&rect, data->DwStyle, FALSE, data->DwExStyle); + ::SetWindowPos(data->Hwnd, NULL, rect.left, rect.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); +} + +static ImVec2 ImGui_ImplWin32_GetWindowSize(ImGuiViewport* viewport) +{ + ImGuiPlatformDataWin32* data = (ImGuiPlatformDataWin32*)viewport->PlatformUserData; + IM_ASSERT(data->Hwnd != 0); + RECT rect; + ::GetClientRect(data->Hwnd, &rect); + return ImVec2(float(rect.right - rect.left), float(rect.bottom - rect.top)); +} + +static void ImGui_ImplWin32_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) +{ + ImGuiPlatformDataWin32* data = (ImGuiPlatformDataWin32*)viewport->PlatformUserData; + IM_ASSERT(data->Hwnd != 0); + data->ExternalResize = true; + RECT rect = { 0, 0, (LONG)size.x, (LONG)size.y }; + ::AdjustWindowRectEx(&rect, data->DwStyle, FALSE, data->DwExStyle); // Client to Screen + ::SetWindowPos(data->Hwnd, NULL, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); + data->ExternalResize = false; +} + +static void ImGui_ImplWin32_SetWindowTitle(ImGuiViewport* viewport, const char* title) +{ + ImGuiPlatformDataWin32* data = (ImGuiPlatformDataWin32*)viewport->PlatformUserData; + IM_ASSERT(data->Hwnd != 0); + ::SetWindowTextA(data->Hwnd, title); +} + +static LRESULT CALLBACK ImGui_ImplWin32_WndProcHandler_PlatformWindow(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) + return true; + + if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle((void*)hWnd)) + { + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformDataWin32* data = (ImGuiPlatformDataWin32*)viewport->PlatformUserData; + switch (msg) + { + case WM_CLOSE: + viewport->PlatformRequestClose = true; + return 0; + case WM_MOVE: + viewport->PlatformOsDesktopPos = ImVec2((float)(short)LOWORD(lParam), (float)(short)HIWORD(lParam)); + break; + case WM_NCHITTEST: + // Let mouse pass-through the window, this is used while e.g. dragging a window, we creates a temporary overlay but want the cursor to aim behind our overlay. + if (viewport->Flags & ImGuiViewportFlags_NoInputs) + return HTTRANSPARENT; + break; + case WM_SIZE: + if (!data->ExternalResize) + viewport->PlatformRequestResize = true; + if (io.RendererInterface.ResizeViewport) + io.RendererInterface.ResizeViewport(viewport, (int)LOWORD(lParam), (int)HIWORD(lParam)); + break; + } + } + + return DefWindowProc(hWnd, msg, wParam, lParam); +} + +static void ImGui_ImplWin32_InitPlatformInterface() +{ + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = ImGui_ImplWin32_WndProcHandler_PlatformWindow; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = ::GetModuleHandle(NULL); + wcex.hIcon = NULL; + wcex.hCursor = NULL; + wcex.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1); + wcex.lpszMenuName = NULL; + wcex.lpszClassName = _T("ImGui Platform"); + wcex.hIconSm = NULL; + ::RegisterClassEx(&wcex); + + // Register platform interface (will be coupled with a renderer interface) + ImGuiIO& io = ImGui::GetIO(); + io.PlatformInterface.CreateViewport = ImGui_ImplWin32_CreateViewport; + io.PlatformInterface.DestroyViewport = ImGui_ImplWin32_DestroyViewport; + io.PlatformInterface.ShowWindow = ImGui_ImplWin32_ShowWindow; + io.PlatformInterface.SetWindowPos = ImGui_ImplWin32_SetWindowPos; + io.PlatformInterface.GetWindowPos = ImGui_ImplWin32_GetWindowPos; + io.PlatformInterface.SetWindowSize = ImGui_ImplWin32_SetWindowSize; + io.PlatformInterface.GetWindowSize = ImGui_ImplWin32_GetWindowSize; + io.PlatformInterface.SetWindowTitle = ImGui_ImplWin32_SetWindowTitle; + + // Register main window handle + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + ImGuiPlatformDataWin32* data = IM_NEW(ImGuiPlatformDataWin32)(); + data->Hwnd = g_hWnd; + main_viewport->PlatformUserData = data; + main_viewport->PlatformHandle = (void*)data->Hwnd; +} + +static void ImGui_ImplWin32_ShutdownPlatformInterface() +{ + ImGuiIO& io = ImGui::GetIO(); + memset(&io.PlatformInterface, 0, sizeof(io.PlatformInterface)); + + ::UnregisterClass(_T("ImGui Platform"), ::GetModuleHandle(NULL)); +}