ocornut/imgui

Same-frame layout evaluation

Opened this issue · 2 comments

ssh4net commented

Version/Branch of Dear ImGui:

Version 1.92.4, Branch: docking

Back-ends:

imgui_impl_glfw.cpp + imgui_impl_vulkan.cpp

Compiler, OS:

windows 11

Full config/build information:

No response

Details:

Question is more about find a proper way to evaluate app GUI layout in a same-frame.

App has main window defined as MainDockSpace where I also define “Process”, “Info”, “Props” and “Image” windows dock layout for default look.
All windows except Image window has fixed width size.

When app run only Process window docked on the left.
When app open Image app also make visible windows with Info (mouse cords, image size, UI debug information etc.) and Props window for image (shader) manipulations or zoom, scroll, grid etc.

I have use Demo for Vulkan/GLFW as a reference and
Docking space, Menus, AppWindows run inside ImGUI::NewFrame / Render().

In code I try to utilize public ImGUI API but for image scale (zoom or fit to window) still need have window size from ImGUI.

Issues I had found at this moment:

  • Even if Info and Props windows defined and initializing before Image Window and Process window already in place. Image windows after Begin get size equal of empty space in DockingSpace without Info/Props.
    Both are docked by default and have fixed size, so ImGui in theory can correctly evaluate available area wrt Process, Info and Props windows.
    So in first frame I don’t have correct sizes from ImGui.
  • second NewFrame pass, ImageWindow still don’t get correct sizes from imgui and Info and Props still did not rendering, as well as ImageWindow
  • only on third frame, I get all sizes app need to evaluate correct image size to fit it into a window and on Render() finally visible all windows.

At this moment I have a counter for image open that re-evaluate image size untill third frame. And I have feels that this is suboptimal. Because also have some issues with zooming and scroll positions.

I have an idea to use “dummy frames” and run ImGui::NewFrame() several times without call Render()
Maybe with some suppression for time/frame count. Only to force system re-evaluate gui in same frame for interactive events.

Or maybe there are more straight forward way to finish gui layout in same frame?

ocornut commented

Your words are a little difficult to understand, to make connecting elements could you provide a shot/video capture for context and a minimum code repro that exhibit the problem, so we can look at what you are doing code wise.

ssh4net commented

Same logic as in imgui demo:

...
        // Start the Dear ImGui frame
        ImGui_ImplVulkan_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();

        ImGuiStyle& style               = ImGui::GetStyle();
        style.DockingNodeHasCloseButton = false;

        Application();

        // Rendering
        ImGui::Render();
...

where Application();

void
Application()
{
    RemoveClosed();
    MainDockSpace();

    KeyboardIO();
    MouseWheel();

    Pipelines();
    Settings();

    int count = static_cast<int>(m_windows.size());

    if (count > 0 && m_pmg_state.LastFocusedWindow_idx >= 0 && m_pmg_state.LastFocusedWindow_idx < count) {
        PropsWindow(m_pmg_state.LastFocusedWindow_idx);
        InfoWindow(m_pmg_state.LastFocusedWindow_idx);
        RenderOrbWidgetControl(m_pmg_state.LastFocusedWindow_idx);
    }

    for (int i = 0; i < count; ++i) {
        ImageWindow(i);
    }
}

MainDockSpace()

...
    ImGui::Begin("MainDockSpace", nullptr, window_flags);

    AppMenuBar();

   if (node && node->IsEmpty()) {
       ImGui::DockBuilderRemoveNode(m_pmg_state.images_docks_id);
       ImGuiID dock_main_id = ImGui::DockBuilderAddNode(m_pmg_state.images_docks_id, ImGuiDockNodeFlags_None);
       ImGui::DockBuilderSetNodeSize(dock_main_id, viewport->Size);

       ImGuiID split_area_id;
       ImGuiID dock_pipeline_id, dock_image_id, dock_info_id, dock_props_id;

       ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Left, 0.5f, &dock_pipeline_id, &split_area_id);
       ImGui::DockBuilderSplitNode(split_area_id, ImGuiDir_Right, 0.5f, &dock_props_id, &split_area_id);
       ImGui::DockBuilderSplitNode(split_area_id, ImGuiDir_Right, 0.5f, &dock_info_id, &dock_image_id);

       // Mark the ImageWindowProps node as central node
       ImGuiDockNode* image_node = ImGui::DockBuilderGetNode(dock_image_id);
       if (image_node) {
           image_node->SetLocalFlags(image_node->LocalFlags | ImGuiDockNodeFlags_CentralNode);
       }

       ImGui::DockBuilderSetNodeSize(dock_pipeline_id, ImVec2(240.0f, viewport->Size.y));
       ImGui::DockBuilderSetNodeSize(dock_props_id, ImVec2(260.0f, viewport->Size.y));
       ImGui::DockBuilderSetNodeSize(dock_info_id, ImVec2(300.0f, viewport->Size.y));

       // Assign windows to dock nodes
       ImGui::DockBuilderDockWindow("Processing", dock_pipeline_id);
       ImGui::DockBuilderDockWindow("Image Info", dock_info_id);
       ImGui::DockBuilderDockWindow("Image Properties", dock_props_id);
       // ImageWindow(s) will dock to dock_image_id (center)

       ImGui::DockBuilderFinish(m_pmg_state.images_docks_id);
   }

   m_pmg_state.images_docks_node = ImGui::DockBuilderGetNode(m_pmg_state.images_docks_id);

   ImGui::End();
 
...

InfoWindow()

void
nfoWindow(const int idx)
{
    if (idx < 0 || idx >= static_cast<int>(m_windows.size()))
        return;

    if (!m_pmg_state.image_info_visible)
        return;

    ImageGuiProps& img_props   = *m_windows[idx].props;
    ImageWindowProps& m_window = *m_windows[idx].window;
    Image& image               = *m_windows[idx].image;

    ImVec2 fixedSizeMin = { 300.0f, -1.0f };
    ImVec2 fixedSizeMax = { 300.0f, FLT_MAX };

    ImGui::PushFont(m_font.f, 14.0f);

    ImGui::SetNextWindowSizeConstraints(fixedSizeMin, fixedSizeMax);
    ImGui::SetNextWindowSize(fixedSizeMin, ImGuiCond_Always);

    ImGuiWindowClass wc;
    wc.DockNodeFlagsOverrideSet |= ImGuiDockNodeFlags_NoResize;  // disable splitter move on this node
    ImGui::SetNextWindowClass(&wc);

    ImGui::Begin("Image Info", &m_pmg_state.image_info_visible);
...

    ImGui::End();
}

ImageWindow()

void
ImageWindow(const int idx)
{
    if (idx < 0 || idx >= static_cast<int>(m_windows.size()))
        return;

    Image& image               = *m_windows[idx].image;
    ImageGuiProps& img_props   = *m_windows[idx].props;
    ImageWindowProps& m_window = *m_windows[idx].window;

    if (!m_window.visible)
        return;

    if (!image.ready)
        return;

    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));

    ImageWindowLayout(idx); // Here i estimate image size using properties i have got from a previous frame

    ImGui::SetNextWindowDockID(m_pmg_state.images_docks_id, ImGuiCond_FirstUseEver);

    std::string window_title = m_window.title;

    ImGui::Begin(window_title.c_str(), &m_window.visible,
                 ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoSavedSettings);

...

    RenderImage(m_window.avail_size, img_props, m_window, idx, image);

// Here i store some window properties like content size, available size, scroll position etc.
// ...
// ...

    ImGui::End();
    ImGui::PopStyleVar();

}

In RenderImage() i use ImGui::BeginGroup();
and AddCallback() for custom shader with image processing or other shaders.