Imgui: Splitter

Created on 4 Sep 2015  路  22Comments  路  Source: ocornut/imgui

Hi
is it possible to add splitter between 2 widgets ? . if no then i hope you consider it as a todo :) , because changing layout space in realtime is really good thing .

enhancement layout useful widgets

Most helpful comment

Reused this idiom in an app. The code is app specific here, we'd want to formalize into an official API call once we can figure out an api to handle n-part splitting.

  • Using a dedicated function instead of Button() would allow to remove a one-frame lag with the drawing, which I am avoiding here by not drawing anything when mouse button is held, which is an ok workaround.
  • The colors could be moved to style (not sure about that?).
  • I'm using an internal feature here SetItemAllowOverlap to allow widget to cover the splitter, that also could be exposed in the public API. EDIT This is now a public api.
void DrawSplitter(int split_vertically, float thickness, float* size0, float* size1, float min_size0, float min_size1)
{
    ImVec2 backup_pos = ImGui::GetCursorPos();
    if (split_vertically)
        ImGui::SetCursorPosY(backup_pos.y + *size0);
    else
        ImGui::SetCursorPosX(backup_pos.x + *size0);

    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
    ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0,0,0,0));          // We don't draw while active/pressed because as we move the panes the splitter button will be 1 frame late
    ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.6f,0.6f,0.6f,0.10f));
    ImGui::Button("##Splitter", ImVec2(!split_vertically ? thickness : -1.0f, split_vertically ? thickness : -1.0f));
    ImGui::PopStyleColor(3);

    ImGui::SetItemAllowOverlap(); // This is to allow having other buttons OVER our splitter. 

    if (ImGui::IsItemActive())
    {
        float mouse_delta = split_vertically ? ImGui::GetIO().MouseDelta.y : ImGui::GetIO().MouseDelta.x;

        // Minimum pane size
        if (mouse_delta < min_size0 - *size0)
            mouse_delta = min_size0 - *size0;
        if (mouse_delta > *size1 - min_size1)
            mouse_delta = *size1 - min_size1;

        // Apply resize
        *size0 += mouse_delta;
        *size1 -= mouse_delta;
    }
    ImGui::SetCursorPos(backup_pos);
}

Usage

DrawSplitter(); // code above
BeginChild(...); // pass width here
EndChild()
SameLine()
BeglnChild(...); // pass width here
EndChild()

mrc-splitter

All 22 comments

EDITED 2018: If you are stumbling on this thread, read the post at the end of the thread, tl;dr; you can use SplitterBehavior in imgui_internal.h as a helper

There's a Columns() API that does some of that, depending exactly what is your use case and what you are trying to do. Splitter is a rather vague term.

For high-level layout you can possibly get away by implementing cheap splitters yourself:
https://github.com/ocornut/imgui/issues/125#issuecomment-135775009

splitter_test

But yes I agree we need better moveable separator in general.

thanks
yes that https://github.com/ocornut/imgui/issues/125#issuecomment-135775009 what i was aiming for
in the example you provide , some times if i click on the separator it didn't move but the whole window moves instead .

Where are you clicking for it to not work? You can replace InvisibleButton() by Button() to see them.

Oops , seems i was trying with an old version of imgui , now i am switching to 1.45 and it works fine now ;)
many thanks

Reused this idiom in an app. The code is app specific here, we'd want to formalize into an official API call once we can figure out an api to handle n-part splitting.

  • Using a dedicated function instead of Button() would allow to remove a one-frame lag with the drawing, which I am avoiding here by not drawing anything when mouse button is held, which is an ok workaround.
  • The colors could be moved to style (not sure about that?).
  • I'm using an internal feature here SetItemAllowOverlap to allow widget to cover the splitter, that also could be exposed in the public API. EDIT This is now a public api.
void DrawSplitter(int split_vertically, float thickness, float* size0, float* size1, float min_size0, float min_size1)
{
    ImVec2 backup_pos = ImGui::GetCursorPos();
    if (split_vertically)
        ImGui::SetCursorPosY(backup_pos.y + *size0);
    else
        ImGui::SetCursorPosX(backup_pos.x + *size0);

    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
    ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0,0,0,0));          // We don't draw while active/pressed because as we move the panes the splitter button will be 1 frame late
    ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.6f,0.6f,0.6f,0.10f));
    ImGui::Button("##Splitter", ImVec2(!split_vertically ? thickness : -1.0f, split_vertically ? thickness : -1.0f));
    ImGui::PopStyleColor(3);

    ImGui::SetItemAllowOverlap(); // This is to allow having other buttons OVER our splitter. 

    if (ImGui::IsItemActive())
    {
        float mouse_delta = split_vertically ? ImGui::GetIO().MouseDelta.y : ImGui::GetIO().MouseDelta.x;

        // Minimum pane size
        if (mouse_delta < min_size0 - *size0)
            mouse_delta = min_size0 - *size0;
        if (mouse_delta > *size1 - min_size1)
            mouse_delta = *size1 - min_size1;

        // Apply resize
        *size0 += mouse_delta;
        *size1 -= mouse_delta;
    }
    ImGui::SetCursorPos(backup_pos);
}

Usage

DrawSplitter(); // code above
BeginChild(...); // pass width here
EndChild()
SameLine()
BeglnChild(...); // pass width here
EndChild()

mrc-splitter

nice. move tutorial will be great!

Hi,

I have try to change the mouse cursor once we are over the split bar, but nothing is changing :

if (GImGui->HoveredIdAllowHoveringOthers && horizontal_splitter)
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);

    else if (GImGui->HoveredIdAllowHoveringOthers && !horizontal_splitter)
        ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);

    else
        ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);

@ghost Sorry I missed this question. Is your binding applying the hardware mouse cursor as requested by ImGui? (by reading GetMouseCursor). ImGui can display a software cursor for you but hardware are much much more pleasant so it is better to avoid them if you can.

FYI

Today I have added a ImGuiButtonFlags_AllowOverlapMode flag to the ButtonEx() function declared in imgui_internal.h. This is useful if you want to create invisible splitter than are using left-over space not used by other widgets, as shown in the bottom splitter in my GIF above.

It previously worked because I was using an alpha of 0.0 for the splitter hovered color, so when hovering a button you wouldn't see that the splitter was actually seeing itself as hovered as well. With that new flag we can now implement a splitter that has a hovered color and still allow for overlapping widgets.

I just modified two lines for correct visual effect:

void Splitter(bool split_vertically, float thickness, float* size0, float* size1, float min_size0, float min_size1, float size = -1);

and

ImGui::Button("##Splitter", ImVec2(!split_vertically ? thickness : size, split_vertically ? thickness : size));

before:
before
after:
after

I'm currently working on splitter toward formalizing Docking #351 features.

If you are using the old splitter patterns, here's a little update for the code to handle mouse going out of limits more gracefully.

Replace:

float mouse_delta = split_vertically ? ImGui::GetIO().MouseDelta.y : ImGui::GetIO().MouseDelta.x;

With:

ImVec2 item_bb = ImGui::GetItemRectMin();
float mouse_delta = split_vertically ? (g.IO.MousePos.y - g.ActiveIdClickOffset.y - item_bb.Min.y) : (g.IO.MousePos.x - g.ActiveIdClickOffset.x - item_bb.Min.x);

I will create a helper for it as it is a common pattern for dragging operations.

FYI the pattern above has been reworked and included in imgui.cpp for internal use (via imgui_internal.h), as:

bool SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f);

You can now reproduce the behavior of the function listed above with a smaller function:
EDITED

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(id, bb, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 0.0f);
}

However I don't think that old signature is good, and not including this facade in the public API. I'm working on a better higher-level system. Mostly posting this here for the records.

Test code:

float h = 200;
static float sz1 = 300;
static float sz2 = 300;
Splitter(true, 8.0f, &sz1, &sz2, 8, 8, h);
BeginChild("1", ImVec2(sz1, h), true);
EndChild();
SameLine();
BeginChild("2", ImVec2(sz2, h), true);
EndChild();

@ocornut here,
bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size2));
should be
bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1));
right? Assuming the first size is the window that comes above.

@nikki93 Correct, I edited my post above. Thanks! (It doesn't affect anything on the git repository as that Splitter() function was just part of my message.)

If you are using SplitterBehavior() from imgui_internal.h, note that I have inverted the 2 first parameters

From
bool ImGui::SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, ...
To
bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, ...

Which is more consistent with other internal functions of the same type.

We had a question re the best way to handle nested child Windows using the splitter with regards to Horizontal layout ? Say we are looking at 1 vertical splitter and each vertical half has multiple horiz splitters. We are trying to create the ability to do the layout from config file and have each container area (Child windows that is not visible) have the ability to add multiple panels (Child windows) with possible splitters in them. If we do them one container at time then the draw cursor is not at the appropriate place so the SameLine() call cannot be used. Should we be using SetCursorPos() to position before drawing every container ? Or is there a better way ? Something like a Horizontal layout stack for all subsequent calls that can be popped to bring us back to line we started ie the original child container window pos ??
SplitterVert1()
BeginChild(V0);
SplitterHoriz1()
BeginChild(H1)
EndChild
SplitterHoriz2()
BeginChild(H2)
EndChild
EndChild();
BeginChild(V1)
SplitterHoriz3()
BeginChild(H3)
EndChild()
EndChild(V1)

Hello @turmansky,
I am not sure I understand your question very precisely, but if SetCursorPos() works for you then that's good. However you may want to look at the Docking branch #2109.

Thx. We will take a look at that branch.
The general question is regarding aligning controls horizontally in groups where drawing is per group rather than across the entire window. SetCursorPos seems to help us in these scenarios but were wondering if there were plans for a horizontal layout stack push/ pop like functionality

if there were plans for a horizontal layout stack push/ pop like functionality

Yes will eventually.

Hey, i am using a Binding for ImGui (ImGui.NET), which is the reason why i can't use imgui_internal.h. So i tried implementing the splitter using the original snippet:

        private void DrawSplitter(bool split_vertically, float thickness, ref float size0, ref float size1,
            float min_size0, float min_size1, float size = -1.0f)
        {
            var backup_pos = ImGui.GetCursorPos();

            if (split_vertically)
                ImGui.SetCursorPosY(backup_pos.Y + size0);
            else
                ImGui.SetCursorPosX(backup_pos.X + size0);

            ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
            ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero);
            ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.6f,0.6f,0.6f,0.1f));

            ImGui.Button("##Splitter", new Vector2(split_vertically ? thickness : size, !split_vertically ? thickness : size));
            ImGui.PopStyleColor(3);

            ImGui.SetItemAllowOverlap(); // This is to allow having other buttons OVER our splitter. 

            if (ImGui.IsItemActive())
            {
                float mouse_delta = split_vertically ? ImGui.GetMouseDragDelta().Y : ImGui.GetMouseDragDelta().X;

                // Minimum pane size
                if (mouse_delta < min_size0 - size0)
                    mouse_delta = min_size0 - size0;
                if (mouse_delta > size1 - min_size1)
                    mouse_delta = size1 - min_size1;

                // Apply resize
                size0 += mouse_delta;
                size1 -= mouse_delta;
            }
            ImGui.SetCursorPos(backup_pos);
        }

The issue is that the original snippet isn't working at all.
So i wanted to ask how long it'll take the splitter functionality to come into core ImGui.

It鈥檚 already in ImGui as you pointed out :)

Maybe see how your binding could optionally expose/export the internal function(s) you want, otherwise it is probably a small patch to add it yourself?

No ETA for making it in public api yet.

By the way, even though the current function differs from the original snippets, looking at your code it seems like it should work. So that鈥檚 also worth investigating!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Folling picture Folling  路  3Comments

ILoveImgui picture ILoveImgui  路  3Comments

noche-x picture noche-x  路  3Comments

ghost picture ghost  路  3Comments

dowit picture dowit  路  3Comments