ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
61.01k stars 10.28k forks source link

Window pops to background when the main App window is moved (Z-order problem) #5847

Open HenryKaufman opened 2 years ago

HenryKaufman commented 2 years ago

Version/Branch of Dear ImGui: Version: 1.89 WIP (18832) Branch: docking imgui_impl_win32 imgui_impl_dx11

Dear ImGui 1.89 WIP (18832)
--------------------------------
sizeof(size_t): 4, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=199711
define: _WIN32
define: _MSC_VER=1916
define: _MSVC_LANG=201402
define: IMGUI_HAS_VIEWPORT
define: IMGUI_HAS_DOCK
--------------------------------

Back-end/Renderer/Compiler/OS Back-ends: _imgui_impl_win32.cpp + imgui_impldx11.cpp Compiler: Visual Studio 2017 Operating System: MS Windows 10 Pro

My Issue/Question:

A window (i.e. ImGui::BeginWindow) behaves nicely until it is add as a child to the main App window. When the main App window is moved (or resized), the child window moves along with it (as expected), but then it pops to the background below all the main window content in the App window and is no longer clickable. The main App window is built with a ImGui::BeginWindow / ImGui::EndWindow sequence that is set up to fill the app window. I tried using the ImGuiWindowFlags_NoBringToFrontOnFocus on the main window to keep it in the background as mentioned in another issue, but that didn't help.

Screenshots/Video

https://user-images.githubusercontent.com/6425918/199611494-4f9c3189-1c9f-4dd6-900f-a04d6d3b7eab.mp4

Standalone, minimal, complete and verifiable example: The complete example below shows the behavior in the video above. (Sorry if it's not minimal enough, but I figured a complete working example would be easier to deal with.) The larger window sticks out of the main window and is moveable. The smaller window inside the main window seems to be behind the full-window ImGui app window so it is unclickable. If you resize the main window so the small window sticks out, it becomes clickable.

If you move either window back inside the main window, they are still on top and clickable. If you just move the main app window, those smaller windows move together with it and are sometimes still clickable. But then if you resize the main window, the windows inside seem to be pushed behind the full-window ImGui app window and become un-clickable. My hope is that this wouldn't happen, or that I could explicitly control the Z-ordering of windows. I understand that is being considered from some of the other github issues I've read.

Thanks for your help!!

// Dear ImGui: standalone example application for DirectX 11
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs

#include "imgui.h"
#include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h"
#include <d3d11.h>
#include <tchar.h>
#include <string>
#include <vector>

// Data
static ID3D11Device*            g_pd3dDevice = NULL;
static ID3D11DeviceContext*     g_pd3dDeviceContext = NULL;
static IDXGISwapChain*          g_pSwapChain = NULL;
static ID3D11RenderTargetView*  g_mainRenderTargetView = NULL;

// Forward declarations of helper functions
bool CreateDeviceD3D(HWND hWnd);
void CleanupDeviceD3D();
void CreateRenderTarget();
void CleanupRenderTarget();
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
float BaseImGuiFontSize = 14.0f;
float TargetImGuiFontSize = 14.0f;
bool sShowVisualizerTab = false;
std::vector<std::string> recentFiles;
bool fileLoaded = true;
bool canSave = true;

// Main code
int main(int, char**)
{
    // Create application window
    //ImGui_ImplWin32_EnableDpiAwareness();
    WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, L"ImGui Example", NULL };
    ::RegisterClassExW(&wc);
    HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX11 Example", WS_OVERLAPPEDWINDOW, 100, 100, 500, 800, NULL, NULL, wc.hInstance, NULL);

    // Initialize Direct3D
    if (!CreateDeviceD3D(hwnd))
    {
        CleanupDeviceD3D();
        ::UnregisterClassW(wc.lpszClassName, wc.hInstance);
        return 1;
    }

    // Show the window
    ::ShowWindow(hwnd, SW_SHOWDEFAULT);
    ::UpdateWindow(hwnd);

    // Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;

#define MY_IMGUI_DOCKING 1
#define MY_IMGUI_VIEWPORTS 1

#ifdef MY_IMGUI_DOCKING
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
    io.ConfigDockingWithShift = true;
    io.ConfigDockingTransparentPayload = true;
#endif

#ifdef MY_IMGUI_VIEWPORTS
    io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;         // Enable Multi-Viewport / Platform Windows, ability to drag a window outside the app window
    io.ConfigViewportsNoDecoration = true;       // don't add Windows title bars on top of imgui title bars when the windows are outside the app window
#endif

    io.ConfigWindowsResizeFromEdges = true;      // allow drag on edges to resize windows
    io.ConfigWindowsMoveFromTitleBarOnly = true; // no dragging from the middle of the window to move the window

    // Setup Dear ImGui style
    ImGui::StyleColorsDark();
    //ImGui::StyleColorsClassic();

    // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
    ImGuiStyle& style = ImGui::GetStyle();
    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
    {
        style.WindowRounding = 0.0f;
        //style.Colors[ImGuiCol_WindowBg].w = 1.0f;
    }

    // Setup Platform/Renderer backends
    ImGui_ImplWin32_Init(hwnd);
    ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);

    // Load Fonts

    // Our state
    bool show_demo_window = true;
    bool show_another_window = true;
    ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
    ImVec2 sViewPortPos = { 0,0 };
    ImVec2 sViewPortSize = { 1000,1000 };

    // Main loop
    bool done = false;
    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, NULL, 0U, 0U, PM_REMOVE))
        {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
            if (msg.message == WM_QUIT)
                done = true;
        }
        if (done)
            break;

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

        // Note that sViewPortPos and sViewPortSize are set below after ImGui::Render() so that they 
        // can query the display size to make the main window the full size of the main App window.
        ImGui::SetNextWindowPos(sViewPortPos, ImGuiCond_Always);
        ImGui::SetNextWindowSize(sViewPortSize, ImGuiCond_Always);

        //Create the main window, which all subsequent windows get drawn inside
        ImGui::Begin("MainWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoBackground |
            ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
            ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoBringToFrontOnFocus);

        ImGui::BeginMenuBar();
        if (ImGui::BeginMenu("File")) {
            // TODO: turn off disabling load if a file is already loaded - by clearing out the state before loading a new file
            if (ImGui::MenuItem("Load .json...", nullptr, false /* selected */, !fileLoaded /* enabled */)) {
            }
            if (ImGui::MenuItem("Save .json", "CTRL+S", false /* selected */, canSave /* enabled */)) {
            }
            if (ImGui::MenuItem("Save .json As...", "", false /* selected */, canSave /* enabled */)) {
            }

            if (ImGui::BeginMenu("Load Recent...", !fileLoaded && !recentFiles.empty()/* enabled */)) {
                static int recentFileIndex = -1;
                for (int i = 0; i < static_cast<int>(recentFiles.size()); ++i) {
                    if (ImGui::MenuItem(recentFiles[i].c_str(), nullptr, false /* selected */, !fileLoaded /* enabled */)) {
                    }
                }
                ImGui::EndMenu();
            }

            if (ImGui::MenuItem("Save All...", "SHIFT+CTRL+S", false /* selected */, canSave /* enabled */)) {
            }

            if (ImGui::MenuItem("Preferences...")) {
            }

            if (ImGui::MenuItem("About...")) {
            }

            if (ImGui::MenuItem("Quit", "CTRL+Q")) {
            }
            ImGui::EndMenu();                                                          // only call EndMenu() if BeginMenu() returns true!
        }

        if (ImGui::BeginMenu("Edit")) {
            if (ImGui::MenuItem("Undo", "CTRL+Z", false /* selected */, true /* enabled */)) {
            }
            if (ImGui::MenuItem("Redo", "CTRL+Y", false /* selected */, true /* enabled */)) {
            }
            if (ImGui::MenuItem("Clear All Completed", nullptr, false /* selected */, true /* enabled */)) {
            }
            if (ImGui::IsItemHovered()) {
                ImGui::SetTooltip("Clear all completed checkboxes:\nThis action is not undoable.");
            }

            ImGui::EndMenu();                                                          // only call EndMenu() if BeginMenu() returns true!
        }

            if (ImGui::Button("Save All")) {
            }

            if (ImGui::Button("Update")) {
            }

        int id = 2398467;
        // Up and Down arrows to set the font size of the whole UI
        ImGui::SameLine();
        //_PushImGuiFont(3);
        {
            ImGui::PushID(id++);
            if (ImGui::Button("-"/*ICON_FA_ARROW_ALT_CIRCLE_DOWN*/)) { // Decrease font size by 1 unit
                //_ShrinkImGuiFonts();
            }
            if (ImGui::IsItemHovered()) {
                ImGui::SetTooltip("Shrink Fonts");
            }
            ImGui::PopID();

            ImGui::SameLine();
            ImGui::PushID(id++);
            if (ImGui::Button("+" /*ICON_FA_ARROW_ALT_CIRCLE_UP*/)) { // Increase font size by 1 unit
                //_GrowImGuiFonts();
            }
            if (ImGui::IsItemHovered()) {
                ImGui::SetTooltip("Grow Fonts");
            }
            ImGui::PopID();
        }
        //ImGui::PopFont();

            if (ImGui::Button("Back To Vehicle Type Selection"))
            {
            }
            ImGui::SameLine();
            if (ImGui::Button(sShowVisualizerTab ? "Hide Visualizer" : "Show Visualizer"))
            {
                sShowVisualizerTab = !sShowVisualizerTab;
            }

            ImGui::SameLine();
                ImGui::Text("Track ID:");
                ImGui::SameLine();
                ImGui::SetNextItemWidth(20 * TargetImGuiFontSize);
                static char trackIdBuf[200];
                ImGui::InputText("##trackId", trackIdBuf, 200);

            ImGui::SameLine();
                ImGui::Text("Track Type:");
                ImGui::SameLine();
                ImGui::SetNextItemWidth(9 * TargetImGuiFontSize);
                static char trackTypeBuf[200];
                ImGui::InputText("##trackType", trackTypeBuf, 200);

        ImGui::EndMenuBar();

        ImGui::End();

        // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
        {
            static float f = 0.0f;
            static int counter = 0;
            ImVec2 winPos1 = { 600, 400 };
            ImGui::SetNextWindowPos(winPos1, ImGuiCond_Appearing);

            ImGui::Begin("Bigger Window");                          // Create a window called "Hello, world!" and append into it.

            ImGui::Text("This is some useful text.");               // Display some text (you can use a format strings too)
            ImGui::Checkbox("Demo Window", &show_demo_window);      // Edit bools storing our window open/close state
            ImGui::Checkbox("Another Window", &show_another_window);

            ImGui::SliderFloat("float", &f, 0.0f, 1.0f);            // Edit 1 float using a slider from 0.0f to 1.0f
            ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color

            if (ImGui::Button("Button"))                            // Buttons return true when clicked (most widgets return true when edited/activated)
                counter++;
            ImGui::SameLine();
            ImGui::Text("counter = %d", counter);

            ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
            ImGui::End();
        }

        // 3. Show another simple window.
        if (show_another_window)
        {
            ImVec2 winPos2 = { 380, 700 };
            ImGui::SetNextWindowPos(winPos2, ImGuiCond_Appearing);

            ImGui::Begin("Smaller Window", &show_another_window);   // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
            ImGui::Text("Hello from another window!");
            if (ImGui::Button("Close Me"))
                show_another_window = false;
            ImGui::End();
        }

        // Rendering
        ImGui::Render();

        // Set the App's main window size by querying the display area
        // This makes the main window fill the whole main window of the App
        if (ImGui::GetDrawData()) {
            sViewPortPos = ImGui::GetDrawData()->DisplayPos;
            sViewPortSize = ImGui::GetDrawData()->DisplaySize;
        }

        const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w };
        g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, NULL);
        g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha);
        ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());

        // Update and Render additional Platform Windows
        if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
        {
            ImGui::UpdatePlatformWindows();
            ImGui::RenderPlatformWindowsDefault();
        }

        g_pSwapChain->Present(1, 0); // Present with vsync
        //g_pSwapChain->Present(0, 0); // Present without vsync
    }

    // Cleanup
    ImGui_ImplDX11_Shutdown();
    ImGui_ImplWin32_Shutdown();
    ImGui::DestroyContext();

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

    return 0;
}

// Helper functions

bool CreateDeviceD3D(HWND hWnd)
{
    // Setup swap chain
    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.BufferCount = 2;
    sd.BufferDesc.Width = 0;
    sd.BufferDesc.Height = 0;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = hWnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;
    sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    UINT createDeviceFlags = 0;
    //createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
    D3D_FEATURE_LEVEL featureLevel;
    const D3D_FEATURE_LEVEL featureLevelArray[2] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, };
    if (D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext) != S_OK)
        return false;

    CreateRenderTarget();
    return true;
}

void CleanupDeviceD3D()
{
    CleanupRenderTarget();
    if (g_pSwapChain) { g_pSwapChain->Release(); g_pSwapChain = NULL; }
    if (g_pd3dDeviceContext) { g_pd3dDeviceContext->Release(); g_pd3dDeviceContext = NULL; }
    if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = NULL; }
}

void CreateRenderTarget()
{
    ID3D11Texture2D* pBackBuffer;
    g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
    g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_mainRenderTargetView);
    pBackBuffer->Release();
}

void CleanupRenderTarget()
{
    if (g_mainRenderTargetView) { g_mainRenderTargetView->Release(); g_mainRenderTargetView = NULL; }
}

#ifndef WM_DPICHANGED
#define WM_DPICHANGED 0x02E0 // From Windows SDK 8.1+ headers
#endif

// Forward declare message handler from imgui_impl_win32.cpp
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// Win32 message handler
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
        return true;

    switch (msg)
    {
    case WM_SIZE:
        if (g_pd3dDevice != NULL && wParam != SIZE_MINIMIZED)
        {
            CleanupRenderTarget();
            g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, 0);
            CreateRenderTarget();
        }
        return 0;
    case WM_SYSCOMMAND:
        if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu
            return 0;
        break;
    case WM_DESTROY:
        ::PostQuitMessage(0);
        return 0;
    case WM_DPICHANGED:
        if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports)
        {
            //const int dpi = HIWORD(wParam);
            //printf("WM_DPICHANGED to %d (%.0f%%)\n", dpi, (float)dpi / 96.0f * 100.0f);
            const RECT* suggested_rect = (RECT*)lParam;
            ::SetWindowPos(hWnd, NULL, suggested_rect->left, suggested_rect->top, suggested_rect->right - suggested_rect->left, suggested_rect->bottom - suggested_rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
        }
        break;
    }
    return ::DefWindowProc(hWnd, msg, wParam, lParam);
}
HenryKaufman commented 1 year ago

Any ideas about this one? Is there something wrong with the way I wrote up the issue?

PathogenDavid commented 1 year ago

Is there something wrong with the way I wrote up the issue?

A large wall of code puts the burden on us to read through it all and try to discern what might be going wrong. (Or to guess and make our own minimal repro.)

(Sorry if it's not minimal enough, but I figured a complete working example would be easier to deal with.)

The idea from the minimal reproducible example is that you can give us something we can copy+paste into one of the official Dear ImGui example applications to reproduce the bug.

It also gives us some confidence you've spent the effort of trying to whittle things down to the basics as part of your own effort to debug things.

For example, your code has a bunch of stuff relating to your main menu bar boilerplate, Direct3D setup, and Font Awesome. All of which are very unlikely to be related to the problem at hand and just serve as noise.