Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Splitter #319

Open
codz01 opened this issue Sep 4, 2015 · 23 comments
Open

Splitter #319

codz01 opened this issue Sep 4, 2015 · 23 comments

Comments

@codz01
Copy link

codz01 commented Sep 4, 2015

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 .

@ocornut
Copy link
Owner

ocornut commented Sep 4, 2015

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:
#125 (comment)

splitter_test

@ocornut
Copy link
Owner

ocornut commented Sep 4, 2015

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

@codz01
Copy link
Author

codz01 commented Sep 4, 2015

thanks
yes that #125 (comment) 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 .

@ocornut
Copy link
Owner

ocornut commented Sep 4, 2015

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

@codz01
Copy link
Author

codz01 commented Sep 4, 2015

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

@ocornut ocornut changed the title splitter Splitter Oct 12, 2015
@ocornut
Copy link
Owner

ocornut commented Oct 12, 2015

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

@ocornut ocornut added the layout label Nov 15, 2015
@idodoyo
Copy link

idodoyo commented Dec 1, 2015

nice. move tutorial will be great!

@ghost
Copy link

ghost commented Dec 7, 2015

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);

@ocornut
Copy link
Owner

ocornut commented May 1, 2016

@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.

@colesnicov
Copy link

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

@ocornut
Copy link
Owner

ocornut commented Nov 8, 2017

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.

ocornut added a commit that referenced this issue Nov 9, 2017
…orarily activating widgets on click before they have been correctly double-hovered. (#319, #600)
@ocornut
Copy link
Owner

ocornut commented Nov 20, 2017

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();

@nikki93
Copy link

nikki93 commented Apr 3, 2018

@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.

@ocornut
Copy link
Owner

ocornut commented Apr 3, 2018

@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.)

ocornut added a commit that referenced this issue Jul 25, 2018
ocornut added a commit that referenced this issue Jul 26, 2018
@ocornut
Copy link
Owner

ocornut commented Jul 26, 2018

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.

@turmansky
Copy link

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)

@ocornut
Copy link
Owner

ocornut commented Oct 15, 2018

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.

@turmansky
Copy link

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

@ocornut
Copy link
Owner

ocornut commented Oct 15, 2018

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

Yes will eventually.

@feliwir
Copy link

feliwir commented Nov 18, 2018

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.

@ocornut
Copy link
Owner

ocornut commented Nov 18, 2018 via email

@ocornut
Copy link
Owner

ocornut commented Nov 18, 2018 via email

@ocornut
Copy link
Owner

ocornut commented Nov 7, 2023

Please note that #1710 just added support for resizable child windows.
It this not the same thing as a splitter, because you are resizing one specific window rather than the separator between two things.
It however can be used advantageously in many places where a splitter would be used.

e.g. "Demo->Examples->Simple Layout" now does:

ImGui::BeginChild("left pane", ImVec2(150, 0), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX);
for (int i = 0; i < 100; i++)
{
    char label[128];
    sprintf(label, "MyObject %d", i);
    if (ImGui::Selectable(label, selected == i))
        selected = i;
}
ImGui::EndChild();
ImGui::SameLine();
....
ImGui::BeginChild("item view", ....);

child_resizable2

Double-clicking fits to required size.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants