ocornut / imgui

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

How can I change a child window of a certain size (this window can be resizable by the user himself) in relation to the system(host) window resolution? #6958

Closed Nitr0-G closed 10 months ago

Nitr0-G commented 10 months ago

Version/Branch of Dear ImGui:

Version: 1.89.9 Branch: master

Back-end/Renderer/Compiler/OS Back-ends: imgui_impl_win32.cpp + imgui_impl_opengl3.cpp Compiler: MSVC/Clang-cl Operating System: Windows 10

My Issue/Question: Hello to all readers!

At the moment I have this IMG It should be moved to the place of the red square and be proportional to the first two child windows when i change resolution of host window. I understand that the position of x and y is statically set, but the whole problem is that I don't understand how I can update this static variable, because if I set 0, then it will stretch to the right size, but I won't know the X or Y axis and I won't be able to change the window size within X and Y via splitters.

I'm sorry that there is so much code, but I can't give you less, because the context of the task will be lost. Thanks in advance to everyone who will take the time and suggest possible solutions to this problem.

I will be glad if someone helps, then after two days of trying I can't do it and my ideas are exhausted. In fact, you need to do something like a formula for calculating the static size depending on the size of the window, but how will it look? Or maybe there is a ready-made function/flag in imgui for this?

P.S.I apologize for, most likely, a stupid question, but I've never really done interfaces before.

Standalone, minimal, complete and verifiable example: (the screenshot shows how after changing the screen resolution, the square itself does not expand in resolution, since my x and y are static, but if I make them dynamic, that is, I put zero instead of x, then the user cannot resize windows through the splitter, and the MainCPUDisasm window begins to show crash, since it fills the entire screen, but if you give it static coordinates, then it is normally displayed as in the screenshot, but does not have a proportional resolution to the resolution of the host window) I have a similar code

void RenderDisasmWindow(_In_ ImVec2 AvailSize, _In_ static float MainCPUDisasmW)
{
    static float DisasmSizeH = AvailSize.y * 0.65f;
    static float InfoTableSizeH = AvailSize.y * 0.05f;
    ImVec2 InfoTableSize(MainCPUDisasmW, InfoTableSizeH);//ImGui::GetFontSize() * 0.05f);
    ImVec2 DisasmSize(MainCPUDisasmW, DisasmSizeH);//ImGui::GetFontSize() * 50);

    if (ImGui::BeginChild("Disasm", DisasmSize, true))
    {
        renderNotesWindow();
        ImGui::EndChild();
    }

    ImGui::InvisibleButton("hsplitter1", ImVec2(AvailSize.x * 1.0f, AvailSize.x * 0.005f), ImGuiButtonFlags_MouseButtonLeft);
    if (ImGui::IsItemActive())
    {
        DisasmSizeH += ImGui::GetIO().MouseDelta.y;
        InfoTableSizeH -= ImGui::GetIO().MouseDelta.y;
    }

    if (ImGui::BeginChild("Info Table", InfoTableSize, true))
    {
        renderNotesWindow();
        ImGui::EndChild();
    }
}

void RenderRegisterWindow(_In_ ImVec2 AvailSize, _In_ static float RegistersSizeW)
{
    static float RegistersSizeH = AvailSize.y * 0.52f;
    static float CallConventionSizeH = AvailSize.y * 0.16f;

    ImVec2 RegistersSize(RegistersSizeW, RegistersSizeH);//ImGui::GetFontSize() * 40);
    ImVec2 CallConventionSize(RegistersSizeW, CallConventionSizeH);//ImGui::GetFontSize() * 12);
    if (ImGui::BeginChild("Registers", RegistersSize, true))
    {
        renderNotesWindow();
        ImGui::EndChild();
    }

    ImGui::InvisibleButton("hsplitter2", ImVec2(AvailSize.x * 1.0f, AvailSize.x * 0.005f), ImGuiButtonFlags_MouseButtonLeft);
    if (ImGui::IsItemActive())
    {
        RegistersSizeH += ImGui::GetIO().MouseDelta.y;
        CallConventionSizeH -= ImGui::GetIO().MouseDelta.y;
    }

    if (ImGui::BeginChild("Call convention", CallConventionSize, true))
    {
        renderNotesWindow();
        ImGui::EndChild();
    }
}

void renderCPUWindow()
{
    ImVec2 AvailSize = ImGui::GetContentRegionAvail();

    static float RegistersSizeW = AvailSize.x * 0.14f;
    static float MainCPUDisasmW = AvailSize.x * 0.86f;
    static float MainCPURegistersW = AvailSize.x * 0.14f;
    static float MainCPUDisasmH = AvailSize.y * 0.72f;
    static float MainStackDumpH = AvailSize.y * 0.2f;

    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
    ImVec2 MainCPUDisasm(MainCPUDisasmW, MainCPUDisasmH);
    ImVec2 MainCPURegisters(MainCPURegistersW, MainCPUDisasmH);
    ImVec2 MainStackDump(0, MainStackDumpH);

    if (ImGui::BeginChild("MainCPUDisasm", MainCPUDisasm, false))
    {
        RenderDisasmWindow(AvailSize, MainCPUDisasmW);
        ImGui::EndChild();
    }
    ImGui::SameLine();

    ImGui::InvisibleButton("vsplitter", ImVec2(AvailSize.x * 0.005f, MainCPUDisasmH), ImGuiButtonFlags_MouseButtonLeft);
    if (ImGui::IsItemActive())
    {
        MainCPUDisasmW += ImGui::GetIO().MouseDelta.x;
    }
    ImGui::SameLine();

    if (ImGui::BeginChild("MainCPURegisters", MainCPURegisters, true))
    {
        RenderRegisterWindow(AvailSize, RegistersSizeW);
        ImGui::EndChild();
    }

    ImGui::InvisibleButton("hsplitter", ImVec2(AvailSize.x * 1.0f, AvailSize.x * 0.005f), ImGuiButtonFlags_MouseButtonLeft);
    if (ImGui::IsItemActive())
    {
        MainCPUDisasmH += ImGui::GetIO().MouseDelta.y;
    }

    if (ImGui::BeginChild("MainStackDump", MainStackDump, true))
    {
        RenderStackDumpWindow(AvailSize);
        ImGui::EndChild();
    }
    ImGui::PopStyleVar();
}
GamingMinds-DanielC commented 10 months ago

A few notes:

Here is an example source with mouse cursor manipulation thrown in as a quick reference:

#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>

bool SplitterH( const char* id, int* posX, int width, const ImVec2& posMin, const ImVec2& posMax )
{
    if ( ( posX == nullptr ) || ( posMin.x >= posMax.x ) || ( posMin.y >= posMax.y ) )
        return false;

    ImGui::SetCursorPos( ImVec2( (float)*posX, posMin.y ) );
    ImGui::InvisibleButton( id, ImVec2( (float)width, posMax.y - posMin.y ) );

    if ( ImGui::IsItemHovered() || ImGui::IsItemActive() )
        ImGui::SetMouseCursor( ImGuiMouseCursor_ResizeEW );

    static int startPosX = 0;
    int        lastPosX  = *posX;

    if ( ImGui::IsItemActive() )
    {
        int move = (int)ImGui::GetMouseDragDelta( ImGuiMouseButton_Left ).x;
        if ( move != 0 )
        {
            *posX = startPosX + move;

            if ( *posX < (int)posMin.x )
                *posX = (int)posMin.x;
            if ( *posX > (int)posMax.x - width )
                *posX = (int)posMax.x - width;
        }
    }
    else
        startPosX = *posX;

    return *posX != lastPosX;
}

bool SplitterV( const char* id, int* posY, int height, const ImVec2& posMin, const ImVec2& posMax )
{
    if ( ( posY == nullptr ) || ( posMin.x >= posMax.x ) || ( posMin.y >= posMax.y ) )
        return false;

    ImGui::SetCursorPos( ImVec2( posMin.x, (float)*posY ) );
    ImGui::InvisibleButton( id, ImVec2( posMax.x - posMin.x, (float)height ) );

    if ( ImGui::IsItemHovered() || ImGui::IsItemActive() )
        ImGui::SetMouseCursor( ImGuiMouseCursor_ResizeNS );

    static int startPosY = 0;
    int        lastPosY  = *posY;

    if ( ImGui::IsItemActive() )
    {
        int move = (int)ImGui::GetMouseDragDelta( ImGuiMouseButton_Left ).y;
        if ( move != 0 )
        {
            *posY = startPosY + move;

            if ( *posY < (int)posMin.y )
                *posY = (int)posMin.y;
            if ( *posY > (int)posMax.y - height )
                *posY = (int)posMax.y - height;
        }
    }
    else
        startPosY = *posY;

    return *posY != lastPosY;
}

void DrawFrame()
{
    ImDrawList* drawList = ImGui::GetWindowDrawList();

    ImVec2 posMin = ImGui::GetCursorScreenPos();
    ImVec2 posMax = posMin + ImGui::GetContentRegionAvail();

    drawList->AddRect( posMin + ImVec2( 1.0f, 1.0f ), posMax - ImVec2( 1.0f, 1.0f ), 0xFF808080, 0.0f, 0, 1.0f );
}

void DrawChildWindow( const char* id, const ImVec2& pos, const ImVec2& size )
{
    if ( ( size.x <= 0.0f ) || ( size.y <= 0.0f ) )
        return;

    ImGui::SetCursorPos( pos );

    if ( ImGui::BeginChild( id, size ) )
    {
        DrawFrame();
    }

    ImGui::EndChild();
}

void ExampleSplitter()
{
    if ( ImGui::Begin( "Example Splitter" ) )
    {
        const int splitterSize = 4;

        ImVec2 pos  = ImGui::GetCursorPos();
        ImVec2 size = ImGui::GetContentRegionAvail();

        static float splitter1_relPosX = 0.85f;
        static float splitter2_relPosY = 0.5f;

        int splitter1_posX = (int)( pos.x - 0.5f * (float)splitterSize + splitter1_relPosX * size.x );
        if ( SplitterH( "splitter1", &splitter1_posX, splitterSize, pos, pos + size ) )
            splitter1_relPosX = ( (float)splitter1_posX + 0.5f * (float)splitterSize - pos.x ) / size.x;

        ImVec2 left_pos   = pos;
        ImVec2 left_size  = ImVec2( (float)splitter1_posX - pos.x, size.y );
        ImVec2 right_pos  = ImVec2( left_pos.x + left_size.x + (float)splitterSize, pos.y );
        ImVec2 right_size = ImVec2( pos.x + size.x - right_pos.x, size.y );

        int splitter2_posY = (int)( right_pos.y - 0.5f * (float)splitterSize + splitter2_relPosY * right_size.y );
        if ( SplitterV( "splitter2", &splitter2_posY, splitterSize, right_pos, right_pos + right_size ) )
            splitter2_relPosY = ( (float)splitter2_posY + 0.5f * (float)splitterSize - right_pos.y ) / right_size.y;

        ImVec2 right_top_pos     = right_pos;
        ImVec2 right_top_size    = ImVec2( right_size.x, (float)splitter2_posY - right_pos.y );
        ImVec2 right_bottom_pos  = ImVec2( right_pos.x, right_top_pos.y + right_top_size.y + (float)splitterSize );
        ImVec2 right_bottom_size = ImVec2( right_size.x, right_pos.y + right_size.y - right_bottom_pos.y );

        DrawChildWindow( "child1", left_pos, left_size );
        DrawChildWindow( "child2", right_top_pos, right_top_size );
        DrawChildWindow( "child3", right_bottom_pos, right_bottom_size );
    }

    ImGui::End();
}

Could use lots of improvements still, but I didn't want to put too much time into it.

ocornut commented 10 months ago

There is a SplitterBehavior() function in imgui_internal.h which I'd encourage you to use to implement this case of behavior. I consider #319 still open as we'd need to:

Note that 1.90 is very likely (very soon) to have a way to ask for child windows to be manually resizable from the bottom or right side, which may be an alternative option to use for those. This'll come with support for saved settings.

Nitr0-G commented 10 months ago

There is a SplitterBehavior() function in imgui_internal.h which I'd encourage you to use to implement this case of behavior. I consider #319 still open as we'd need to:

* formalize an API for specifying resizing policy (stretched, fixed, with auto rescaling of fixed components when dpi factor changes etc)

* allow to get that info to easily persist in .ini file.

Note that 1.90 is very likely (very soon) to have a way to ask for child windows to be manually resizable from the bottom or right side, which may be an alternative option to use for those. This'll come with support for saved settings.

There is such a framework as FTXUI(https://github.com/ArthurSonzogni/FTXUI ). Windows and similar information in it are essentially a container, and there I and others, for example, people can quickly arrange windows along their borders on the right, left, bottom or top(https://github.com/ArthurSonzogni/FTXUI/blob/main/examples/component/resizable_split.cpp#L14) (screenshot). So I think, yes, it would be great if a person could align the windows right away. Thank you and other participants for developing imgui.

A few notes:

* `EndChild()` must be called even if the corresponding `BeginChild()` returns false

* you should positions and sizes to integers, otherwise you could get a blurry UI

* splitters are usually a fixed width/height, not relative to the window they are in

* handle all of your splitters first, otherwise there might be a single frame lag for some child windows

* you can set the cursor explicitly for best control

Here is an example source with mouse cursor manipulation thrown in as a quick reference:

Thank you very much for such detailed advice and response! Thanks!