ocornut / imgui

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

Automatically adjust height of a docking node #7631

Open thewoz opened 4 months ago

thewoz commented 4 months ago

Version/Branch of Dear ImGui:

Version 1.90.5, Branch: docking

Back-ends:

ImGui_ImplGlfw ImGui_ImplOpenGL3

Compiler, OS:

macOS

Details:

hello everyone, I am in docking branch and I am developing an application but I don't quite understand how to proceed on one point.

The layout of my window is very simple:

Screenshot 2024-05-28 at 16 39 29

at the bottom of the layout i am creating a sequencer. The content, so the height, of the sequencer changes over time.

I would like to figure out how to automatically adjust the height of the window so that everything is flush.

Screenshot 2024-05-28 at 16 39 42

ocornut commented 4 months ago

I would like to figure out how to automatically adjust the height of the window so that everything is flush.

That might be contradictory with making the bottom part a docking node, aka you cannot expect user to resize this section while making it automatically resizing at all time.

Suggestion 1: We don't have enough information about your setup, but if the setup use a Dockspace() currently you could technically change the vertical split to use BeginChild() with ImGuiChildFlags_ResizeY, however I realize that a rather annoying change as makes the code/logic change based on a seemingly innocuous behavior change.

Suggestion 2: It may be more natural that you directly poke into the dock node where that timeline is docked by finding the ImGuiDockNode* and writing to node->SizeRef.y preferably at the beginning of the frame.

(It's also expected that other constraints in place may prevent that exact value from being full-filed, namely if e.g. the total available height is too small, but that's probably something you'd want)

thewoz commented 4 months ago

hi @ocornut you are absolutely right! I have not given enough information about the setup step and my request actually seems contradictory.

Sorry about that!

My setup, as in the attached photo, consists of three parts.

Screenshot 2024-05-29 at 11 50 41

I also attach a small snippet of the code.

To be clearer, rather than automatically adjusting the height of the bottom part, I would like to put limits on its height. That is, that it cannot be larger than a certain amount. I.e., that it cannot be larger than the contents inside it.

Is there then a way me put these limits and to calculate the size of the content inside it?

    ImGui::Begin("MainWindow", NULL, window_flags);
    ImGui::PopStyleVar();

    float height = ImGui::GetFrameHeight();

    if(ImGui::BeginMenuBar()) {

      if(ImGui::BeginMenu("Menu")) {
        ImGui::MenuItem("Main menu bar", NULL, false, true);
        ImGui::EndMenu();
      }
      ImGui::EndMenuBar();

    }

    if(ImGui::BeginViewportSideBar("StatusBar", viewport, ImGuiDir_Down, height, window_flags)) {
        if (ImGui::BeginMenuBar()) {
            ImGui::Text("status bar");
            ImGui::EndMenuBar();
        }
        ImGui::End();
    }

    ImGuiID mainDockSpaceId = ImGui::GetID("MainDockSpace");
    const ImVec2 dockspace_size = ImGui::GetContentRegionAvail();
    ImGui::DockSpace(mainDockSpaceId, ImVec2(0.0f, 0.0f), dockspace_flags);

    ImGuiWindowClass window_class;
    window_class.DockNodeFlagsOverrideSet = ImGuiDockNodeFlags_NoTabBar;

    ImGui::SetNextWindowClass(&window_class);
    ImGui::Begin("Down", &show_another_window, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
    if(ImGui::BeginNeoSequencer("Sequencer", &currentFrame, &minFrame, &maxFrame)) {
      if(ImGui::BeginNeoGroup("A", &showKeyframes)) { ImGui::EndNeoGroup(); }
      if(ImGui::BeginNeoGroup("B", &showKeyframes)) { ImGui::EndNeoGroup(); }
      if(ImGui::BeginNeoGroup("C", &showKeyframes)) { ImGui::EndNeoGroup(); }
      if(ImGui::BeginNeoGroup("D", &showKeyframes)) { ImGui::EndNeoGroup(); }
      if(ImGui::BeginNeoGroup("E", &showKeyframes)) { ImGui::EndNeoGroup(); }
      ImGui::EndNeoSequencer();
    }
    ImGui::End();

    ImGui::SetNextWindowClass(&window_class);
    ImGui::Begin("Left", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
    GLuint texture;
    glGenTextures( 1, &texture );
    glBindTexture( GL_TEXTURE_2D, texture );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 );
    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, image.cols, image.rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.data );
    ImGui::Image( reinterpret_cast<void*>( static_cast<intptr_t>( texture ) ), ImVec2( image.cols, image.rows ) );
    ImGui::End();

    ImGui::SetNextWindowClass(&window_class);
    ImGui::Begin("Right", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
    if(ImGui::CollapsingHeader("A")){ }
    if(ImGui::CollapsingHeader("B")){ }
    if(ImGui::CollapsingHeader("C")){ }
    if(ImGui::CollapsingHeader("D")){ }
    if(ImGui::CollapsingHeader("E")){ }
    ImGui::End();

    static bool sFirstFrame = true;
    if(sFirstFrame) {

      sFirstFrame = false;

      ImGui::DockBuilderRemoveNode(mainDockSpaceId);
      ImGui::DockBuilderAddNode(mainDockSpaceId, ImGuiDockNodeFlags_None);
      ImGui::DockBuilderSetNodeSize(mainDockSpaceId, dockspace_size);

      ImGuiID dock_id_up;
      ImGuiID dock_id_down;

      ImGuiID dock_id_left;
      ImGuiID dock_id_right;

      ImGui::DockBuilderSplitNode(mainDockSpaceId, ImGuiDir_Up, 0.5f, &dock_id_up, &dock_id_down);

      ImGui::DockBuilderSplitNode(dock_id_up, ImGuiDir_Right, 0.5f, &dock_id_right, &dock_id_left);

      ImGui::DockBuilderDockWindow("Down", dock_id_down);
      ImGui::DockBuilderDockWindow("Left", dock_id_left);
      ImGui::DockBuilderDockWindow("Right", dock_id_right);

      ImGui::DockBuilderFinish(mainDockSpaceId);

    }

    ImGui::End();

    ImGui::Render();
ocornut commented 4 months ago

To be clearer, rather than automatically adjusting the height of the bottom part, I would like to put limits on its height. That is, that it cannot be larger than a certain amount. I.e., that it cannot be larger than the contents inside it.

Sizing constraints are currently not supported for docking nodes (#4228, but also technically #6326, #2849).

Some early work I did for potential support for toolbars might help, see code #2648, namely those lines:

        node->WantLockSizeOnce = true;
        node->Size[toolbar_axis_perp] = node->SizeRef[toolbar_axis_perp] = TOOLBAR_SIZE_WHEN_DOCKED;

But generally I think this is not easy to achieve and currently not worth investigating further, considering your need doesn't seem that important (as user can resize).

As mentioned in my first message, another workaround would be to use a manual splitter #319 or two BeginChild() with the top one using ImGuiChildFlags_ResizeY and a constraint on the top window.

thewoz commented 4 months ago

hi @ocornut , thanks for the support!

I thought that for my case the best thing is to use a manual splitter as you suggest. I wrote some code (which I attach) but I can't fix two issues:

https://github.com/ocornut/imgui/assets/907951/b9771a1b-753d-4774-822d-5b72aced50a7

what am I doing wrong? thank you very much

#include <cstdio>
#include <cstdlib>

#include <iostream>

#include <glad/glad.h>

#include <GLFW/glfw3.h>

#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui/imgui.hpp>

bool Splitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size = -1.0f)
{
    using namespace ImGui;
    ImGuiContext& g = *GImGui;
    ImGuiWindow* window = g.CurrentWindow;
    ImGuiID id = window->GetID("##Splitter");
    ImRect bb;
    bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1));
    bb.Max = bb.Min + CalcItemSize(split_vertically ? ImVec2(thickness, splitter_long_axis_size) : ImVec2(splitter_long_axis_size, thickness), 0.0f, 0.0f);
    return SplitterBehavior(bb, id, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 0.0f);
}

static void glfwErrorCallback(int error, const char * description) {
    fprintf(stderr, "GLFW error (%d): %s\n", error, description);
}

int main(int argc, const char * argv[]) {

  //Attempt to initialize GLFW
  if (!glfwInit()) {
    fprintf(stderr, "GLFW init error\n");
    exit(EXIT_FAILURE);
  }

  // Decide GL+GLSL versions
#if __APPLE__
  // GL 3.2 + GLSL 150
  const char* glsl_version = "#version 100";
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 3.2+ only
  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);            // Required on Mac
#else
  // GL 3.0 + GLSL 130
  const char* glsl_version = "#version 130";
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 3.2+ only
  //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);            // 3.0+ only
#endif

  //Set GLFW's error callback function
  glfwSetErrorCallback(glfwErrorCallback);

  //GLFW creates a window and its OpenGL context with the next function
  GLFWwindow * window = glfwCreateWindow(640, 480, "Idra", NULL, NULL);

  if(!window) {
    std::cerr << "GLFW failed to create a window and/or OpenGL context :(";
    glfwTerminate();
    exit(0);
  }

  //Window creation was successful. Continue
  glfwMakeContextCurrent(window);
  glfwSwapInterval(1); // Enable vsync

  if(!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
    fprintf(stderr, "Failed to initialize GLAD\n");
    abort();
  }

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

  ImGui_ImplGlfw_InitForOpenGL(window, true);
  ImGui_ImplOpenGL3_Init(glsl_version);

  ImGui::StyleColorsDark();

  ImVec4 clear_color = ImColor(60, 55, 15);

  //Set scale based on scale of monitor
  GLFWmonitor * monitor = glfwGetPrimaryMonitor();
  float scale = 2.f;
  glfwGetMonitorContentScale(monitor, &scale, nullptr);

  // The render loop
  while (!glfwWindowShouldClose(window)) {

    glfwPollEvents();

    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();

    ImGuiViewport * viewport = ImGui::GetMainViewport();
    ImGui::SetNextWindowPos(viewport->Pos);
    ImGui::SetNextWindowSize(viewport->Size);
    ImGui::SetNextWindowViewport(viewport->ID);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));

    ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoMove;

    ImGui::Begin("MainWindow", NULL, window_flags);
    ImGui::PopStyleVar();

    float height = ImGui::GetFrameHeight();

    if(ImGui::BeginMenuBar()) {
      if(ImGui::BeginMenu("Menu")) {
        ImGui::MenuItem("Main menu bar", NULL, false, true);
        ImGui::EndMenu();
      }
      ImGui::EndMenuBar();
    }

    if(ImGui::BeginViewportSideBar("StatusBar", viewport, ImGuiDir_Down, height, window_flags)) {
        if (ImGui::BeginMenuBar()) {
            ImGui::Text("status bar");
            ImGui::EndMenuBar();
        }
        ImGui::End();
    }

    //float h = 200;
    static float h1 = 300;
    static float h2 = 300;
    static float w1 = 300;
    static float w2 = 300;

    // horizontal splitter
    Splitter(false, 4.0f, &h1, &h2, 50, 50);

    // create top container green
    ImGui::PushStyleColor(ImGuiCol_ChildBg, ImColor(0,255,0).Value);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
    ImGui::BeginChild("1", ImVec2(0, h1), true);
    ImGui::PopStyleVar();
    ImGui::PopStyleColor();

      // vertical splitter
      Splitter(true, 4.0f, &w1, &w2, 50, 50);

      // right part blue
      ImGui::PushStyleColor(ImGuiCol_ChildBg, ImColor(0,0,255).Value);
      ImGui::BeginChild("3", ImVec2(w1, 0), true);
      ImGui::PopStyleColor();
      ImGui::EndChild();

      ImGui::SameLine();

      // left part red
      ImGui::PushStyleColor(ImGuiCol_ChildBg, ImColor(255,0,0).Value);
      ImGui::BeginChild("4", ImVec2(0, 0), true);
      ImGui::PopStyleColor();
      ImGui::EndChild();

    ImGui::EndChild();

    // bottom part yellow
    ImGui::PushStyleColor(ImGuiCol_ChildBg, ImColor(255,255,0).Value);
    ImGui::BeginChild("2", ImVec2(0, h2), true);
    ImGui::PopStyleColor();

    ImGui::EndChild();

    ImGui::End();

    // Rendering
    ImGui::Render();
    int display_w, display_h;
    glfwGetFramebufferSize(window, &display_w, &display_h);
    glViewport(0, 0, display_w, display_h);
    glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);
    glClear(GL_COLOR_BUFFER_BIT);
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

    glfwSwapBuffers(window);

  }

  // Cleanup
  ImGui_ImplOpenGL3_Shutdown();
  ImGui_ImplGlfw_Shutdown();
  ImGui::DestroyContext();

  glfwDestroyWindow(window);
  glfwTerminate();

  return 0;

}