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

Stack Layout implementation #746

Closed
wants to merge 11 commits into from
Closed

Stack Layout implementation #746

wants to merge 11 commits into from

Conversation

thedmd
Copy link
Contributor

@thedmd thedmd commented Jul 21, 2016

After searching for some layout related solution I decided to pull my own after everything I found was only issues listed on this repository (#97).

I think BeginHorizontal/EndHorizontal API was promissing, so I gave it a shot. There is how code look like and what results it produce.

            ImGui::BeginHorizontal("example_h1", spanToWindow ? bounds : ImVec2(0, 0));
                ImGui::TextUnformatted("Left");
                ImGui::Spring(middleWeight);
                ImGui::TextUnformatted("Middle");
                ImGui::Spring(1.0f - middleWeight);
                ImGui::TextUnformatted("Right");
            ImGui::EndHorizontal();

stack_layout

I tried to bend ImGui to help me layout widgets easly. This is what API I came up with:

    IMGUI_API void BeginHorizontal(const char* str_id, const ImVec2& size = ImVec2(0, 0));
    IMGUI_API void BeginHorizontal(const void* ptr_id, const ImVec2& size = ImVec2(0, 0));
    IMGUI_API void EndHorizontal();
    IMGUI_API void BeginVertical(const char* str_id, const ImVec2& size = ImVec2(0, 0));
    IMGUI_API void BeginVertical(const void* ptr_id, const ImVec2& size = ImVec2(0, 0));
    IMGUI_API void EndVertical();
    IMGUI_API void Spring(float weight = 1.0f, float spacing = -1.0f);

This code works by caching sizes of widgets in first frame and positioning them in next. Layout is evaluated only if something related to size changes.

Code was crafted in a few hours so it may be cruel in some places. I tried to be as consistent with ImGui code style. Please grab this code and try to use it. Let me know what you think about this solution. : )

Example code show one use case. There are however many more. Widgets can be interleaved by Springs which may have zero (sticking widgets together) weight or greater than zero. Free space will be divide according to them. There is an option to provide spacing which acts like in SameLine() function but works for both horizontal and vertical layouts. Layouts can be mixed together, you may put one in another as you can see on example gif.

@@ -381,6 +411,10 @@ struct ImGuiContext
ImVector<ImFont*> FontStack; // Stack for PushFont()/PopFont()
ImVector<ImGuiPopupRef> OpenPopupStack; // Which popups are open (persistent)
ImVector<ImGuiPopupRef> CurrentPopupStack; // Which level of BeginPopup() we are in (reset every frame)
ImGuiLayout* CurrentLayout;
ImVector<ImGuiLayout*> LayoutStack;
ImVector<ImGuiLayout*> HorizontalLayouts;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually split to Horizontal and Vertical layouts is not necessary. These can be held in one vector.

@ocornut
Copy link
Owner

ocornut commented Jul 22, 2016

Thanks Michał, this looks very interesting (and it isn't often that we receive non-trivial PR here).
It may take me a long while to digest and think about this as I'm super busy right now (and also need to it evaluate it from different angle such performance and stability in the context of changing layouts).

Looking forward to look into it as it seems very cool!

Might merge some small parts early (changing some floats to ImVec2) if only to make this PR not break as easily with future changes.

(As a minor niggle I would appreciate if you used xxxx_xxxx casing for local variables.)

@thedmd
Copy link
Contributor Author

thedmd commented Jul 22, 2016

No rush. I will really appreciate your comments on this code.

I have hard time to grasp how ImGui handle cursor positioning. I have areas of code which should move it vertically. Undo last padding without using style (as style may change). These parts of code may need more attention. I don't want to break stuff I do not touch. Demo appears to work.

'ItemSize()' and 'SameLine()' are hard to understand. Every time I think I got it, there is one more variable I have trouble to grasp. I will be more than happy to see simplified logic which hide quirks of managing cursor position. For example ItemSize() implemented in terms of MoveToItemRB (move cursor to bottom right of current widget without padding), MoveToNextLine (move cursor to next line and optionally apply vertical padding). Cursor position is calculated in various places and I'm not sure all of them use same rules. Some places may ignore columns I think.

I will keep updating code. I already found some bugs when combining multiple layouts and I'm bashing them.

And sure, I will change format of local variables.

Bdw. ImGui is quite nice library. Thank you for that.

@ocornut
Copy link
Owner

ocornut commented Jul 22, 2016

Your patch is great if only because it may force me to look at those areas in details (not many people have ventured there after from me), and it probably needs a little cleanup.

One of the confusing thing is the LineTextBaseOffset stuff which only exist so that single line or chunks of text can use g.FontSize without any padding whereas widgets uses FramePadding. This is causing a little confusing and I sometimes considering to remove this and make single lines of text uses FramePadding as well, but it would waste vertical space.

FYI I have this local patch to fix SameLine(X) where X is a >0 in the context of the context of a group. That may tie to your changes somehow.

diff --git a/imgui.cpp b/imgui.cpp
index ad2007b..7abb897 100644
--- a/imgui.cpp
+++ b/imgui.cpp
@@ -4737,6 +4737,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us

         // Setup drawing context
         window->DC.IndentX = 0.0f + window->WindowPadding.x - window->Scroll.x;
+        window->DC.GroupOffsetX = 0.0f;
         window->DC.ColumnsOffsetX = 0.0f;
         window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.IndentX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y);
         window->DC.CursorPos = window->DC.CursorStartPos;
@@ -9806,7 +9807,8 @@ void ImGui::BeginGroup()
     group_data.BackupLogLinePosY = window->DC.LogLinePosY;
     group_data.AdvanceCursor = true;

-    window->DC.IndentX = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffsetX;
+    window->DC.GroupOffsetX = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffsetX;
+    window->DC.IndentX = window->DC.GroupOffsetX;
     window->DC.CursorMaxPos = window->DC.CursorPos;
     window->DC.CurrentLineHeight = 0.0f;
     window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
@@ -9830,6 +9832,7 @@ void ImGui::EndGroup()
     window->DC.CurrentLineHeight = group_data.BackupCurrentLineHeight;
     window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset;
     window->DC.IndentX = group_data.BackupIndentX;
+    window->DC.GroupOffsetX = window->DC.IndentX;
     window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;

     if (group_data.AdvanceCursor)
@@ -9859,7 +9862,7 @@ void ImGui::SameLine(float pos_x, float spacing_w)
     if (pos_x != 0.0f)
     {
         if (spacing_w < 0.0f) spacing_w = 0.0f;
-        window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w;
+        window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffsetX;
         window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
     }
     else
diff --git a/imgui_internal.h b/imgui_internal.h
index 6180891..88b4283 100644
--- a/imgui_internal.h
+++ b/imgui_internal.h
@@ -611,6 +611,7 @@ struct IMGUI_API ImGuiDrawContext
     int                     StackSizesBackup[6];    // Store size of various stacks for asserting

     float                   IndentX;                // Indentation / start position from left of window (increased by TreePush/TreePop, etc.)
+    float                   GroupOffsetX;
     float                   ColumnsOffsetX;         // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API.
     int                     ColumnsCurrent;
     int                     ColumnsCount;

@thedmd
Copy link
Contributor Author

thedmd commented Jul 23, 2016

Thanks for a patch. If it fixes a problem with SameLine(). Now I'm investigating its behavior and on thing isn't obvious. Should two consecutive calls SameLine(0,25) stack to SameLine(0,50)? Current behavior is only last one is applied. Is that intentional or undefined?

image

// drawRect() and fillItemBounds() are at very end of this comment
// if you want to copy&paste this example
drawRect(600, 40, ImColor(255, 0, 0, 128), 2); // 2 is by how many pixel rect should be expanded
ImGui::BeginGroup();
ImGui::Dummy(ImVec2(200, 40));
fillItemBounds(ImColor(255, 128, 128)); // red rect
ImGui::SameLine(0, 25);
ImGui::SameLine(0, 25);
ImGui::Dummy(ImVec2(350, 40));
fillItemBounds(ImColor(128, 255, 128)); // green rect
ImGui::EndGroup();

SameLine() is useful to tie two groups together without changing style and drawing dummy control (also taking into account previous padding). I added SameColumn() (PR #749) function to mimic SameLine().
image

// Vertical
drawRect(40, 400, ImColor(255, 0, 0, 128), 2);
ImGui::BeginGroup();
ImGui::Dummy(ImVec2(40, 100));
ImGui::SameColumn(0, 25);
ImGui::SameColumn(0, 25);
fillItemBounds(ImColor(255, 128, 128));
ImGui::Dummy(ImVec2(40, 250));
fillItemBounds(ImColor(128, 255, 128));
ImGui::EndGroup();

Mentionend utility functions. Make sure you include imgui_internal.h with IMGUI_DEFINE_MATH_OPERATORS defined.

auto fillItemBounds = [](ImColor color, float expand = 0.0f)
{
    ImGui::GetWindowDrawList()->AddRectFilled(
        ImGui::GetItemRectMin() - ImVec2(expand, expand),
        ImGui::GetItemRectMax() + ImVec2(expand, expand),
        color);
};

auto drawRect = [](float w, float h, ImColor color, float expand = 0.0f)
{
    ImGui::GetWindowDrawList()->AddRect(
        ImGui::GetCursorScreenPos() - ImVec2(expand, expand),
        ImGui::GetCursorScreenPos() + ImVec2(w + expand, h + expand),
        color);
};

To avoid question being lost. Does SameLine()/SameColumn() should stack if first argument is zero?
In my opinion the answer is yes, but that will mean current behavior is buggy.

@ocornut
Copy link
Owner

ocornut commented Jul 23, 2016

Should two consecutive calls SameLine(0,25) stack to SameLine(0,50)? Current behavior is only last one is applied. Is that intentional or undefined?

That's undefined doc-wise but to me it was never question of it "stacking". SameLine() is a sort of "undo newline + move X cursor" so by this definition it shouldn't really add up?

@thedmd
Copy link
Contributor Author

thedmd commented Jul 23, 2016

I think I understand what it does. To enforce stacking I can always add dummy widget without size after calling SameLine(). Thanks for pointing me that out.

@thedmd
Copy link
Contributor Author

thedmd commented Jul 24, 2016

I think layouts are now done. Sorry for early rebase. I rashed that out, please do not mind that. What this PR now represent is complete implementation of horizontal and vertical layouts.

How it works. Layout control position of all elements it contains. Elements are:

  • Items build using BeginGroup()/EndGroup() calls, size is captured using GetItemRectXXX() calls
  • Springs created by ImGui::Spring() calls

Let's use horizontal layout as an example. Minimum possible width is sum of widths of all items and sizes of all springs. Minimum height is maximum height of all items.
image

Layout is always trying to generate smallest possible result. However user may provide larger rectangle where layout should fit. This extra free space will be used by springs and distributed equally based on their weighs. If weights of all springs are equal, spaces between items will also be equal.

Items inside layout may have different height. By default they are centered vertically on available space (as on image). Alignment can be controlled using ImGuiStyle.LayoutAlign style variable. It is float value from 0 to 1 range where 0 mean left/top, 1 right/bottom, 0.5 middle/center.
This is how it is controlled now. It is possible control alignment of individual items but function calls look awkward. Maybe separate function ImGui::Align() should be provided?

Springs are special for layouts. They have weight which control how much of free space will be assigned to them. Also they allow to override spacing. By default ImGuiStyle.ItemSpacing is used.

Layouts can stack and automatically use free space from parent layout if any is present.

Layouts are calculated once at the end of frame if any meaningful change is detected. Most of the time values stay as calculated on first pass. Nested layouts may some times require additional pass. Maximum number of frames required to calculate all layouts is equal to number of nested layouts.

Changing of alignment inside layout does not require recalculation.

stack_layout_3

I think I bashed out all bugs. I tried to not touch original ImGui code when possible. Also I fixed naming of local variables. : )
I got rid of SameColumn() as it turn out is not required in this implementation.

I hope you will find time for feedback.

Bonus: All this work was done to enable me to create node editor which is not only easy to use but also easy to craft from code. For example node visible in image is generated using this code:

        ImGui::BeginVertical("node");
            ImGui::BeginHorizontal("header");
                ImGui::Spring(0.0f);
                ImGui::TextUnformatted("Do N");
                ImGui::Spring();
                ImGui::Button("X");
                ImGui::Spring(0.0f);
            ImGui::EndHorizontal();
            drawItemRect(ImColor(0, 0, 0));
            ImGui::Spring(0,4);
            ImGui::BeginHorizontal("content");
                ImGui::Spring(0, 4);
                ImGui::PushStyleVar(ImGuiStyleVar_LayoutAlign, 0.0f);
                ImGui::BeginVertical("inputs");
                    ImGui::Button("X"); ImGui::SameLine(); ImGui::TextUnformatted("Enter");
                    ImGui::Spring(0.0f);
                    ImGui::Button("X"); ImGui::SameLine(); ImGui::TextUnformatted("N");
                    ImGui::Spring(0.0f);
                    ImGui::Button("X"); ImGui::SameLine(); ImGui::TextUnformatted("Reset");
                    ImGui::Spring(1.0f, 0.0f);
                ImGui::EndVertical();
                fillItemRect(ImColor(0, 255, 0, 128));
                ImGui::PopStyleVar();
                ImGui::Spring();
                ImGui::PushStyleVar(ImGuiStyleVar_LayoutAlign, 1.0f);
                ImGui::BeginVertical("outputs");
                    ImGui::TextUnformatted("Exit");    ImGui::SameLine(); ImGui::Button("X");
                    ImGui::Spring(0.0f);
                    ImGui::TextUnformatted("Counter"); ImGui::SameLine(); ImGui::Button("X");
                    ImGui::Spring(1.0f);
                ImGui::EndVertical();
                fillItemRect(ImColor(0, 255, 0, 128));
                ImGui::PopStyleVar();
                ImGui::Spring(0, 4);
            ImGui::EndHorizontal();
        ImGui::EndVertical();
        drawItemRect(ImColor(0, 0, 0), 2);

@thedmd
Copy link
Contributor Author

thedmd commented Jul 25, 2016

Most complex layouts I have are now working as expected. Finally.
image

@thedmd
Copy link
Contributor Author

thedmd commented Jul 26, 2016

Changes:

  • Add 'align' parameter to BeginHorizontal/BeginVertical so default align may be override for single layout scope. Alignment is not inherited by nested layouts.
  • Add SuspendLayout()/ResumeLayout() to restore original ImGui positioning inside layout. This make it possible to create a regular group or display more complicated controls like Combo.

@thedmd
Copy link
Contributor Author

thedmd commented Jul 29, 2016

@ocornut What is your opinion about SuspendLayout() / ResumeLayout() API?

@ocornut
Copy link
Owner

ocornut commented Jul 29, 2016

I'm sorry I probably won't be able to look at this seriously until a long time might be several months. My mind is totally elsewhere, too many open tasks, and I can't judge this in an hour as it is a serious matter, I'd need to seriously make use of it. I will try to make a quick first evaluation sooner if only so that we can have more of a conversation here.

I'm very interested in this and every of your comment/thought as those posted above important so don't hesitate to keep posting here. (this is also why I'm tempted to merge some of the float>ImVec2 to keep it up to date more easily and keep you around :).

My concern from just skimming briefly at the code is that there may be a meaningful cost right now, it is quite heavier than what ImGui typically do. So it can't be added if we imply that the default user behavior should be to use those functions around everything. Nevertheless it is definitively very useful so we should strive to a) optimize it and b) make it clear what the cost are.

Quick bits:

  • Comment above says "Items build using BeginGroup()/EndGroup() calls, size is captured using GetItemRectXXX() calls" but I see no connection in the code between BeginGroup()/EndGroup() and the new layout stuf, I am missing it?
  • The entire concept of Group was a bit tackled on for quick layout purpose and I wouldn't be against changing its definition/purpose if it made more sense in the wider context of having better layout primitives.
  • You may replace the implementation FindLayout() with an ImGuiStorage (key->void*) which amortizes.
  • Why aren't g.Layouts/g.LayoutsStack inside ImGuiWindow? (it would tends to allocate more per-window tho, but I am aiming to add an optional local buffer to ImVector template so all that the per-window buffer that are rarely growing a lot can default to use parent memory).
  • If one can't reopen a layout twice a frame, would it make sense to have a single Items array shared by all layouts, to reduce the number of heap allocations? (It may complexity code, right now I'm just asking to understand the reasoning behind data structures)

@thedmd
Copy link
Contributor Author

thedmd commented Jul 29, 2016

I understand you're busy. No rush.

Layout is recalculated when size of item inside change or it's bounds change. After that it is sitting idle doing nothing.

Comment above says "Items build using BeginGroup()/EndGroup() calls, size is captured using GetItemRectXXX() calls" but I see no connection in the code between BeginGroup()/EndGroup() and the new layout stuf, I am missing it?

If you look at BeginLayout()/EndLayout() and BeginLayoutItem()/EndLayoutItem() you will notice groups are used. Layout is build on top of groups. They are handy to capture size of widgets.

The entire concept of Group was a bit tackled on for quick layout purpose and I wouldn't be against changing its definition/purpose if it made more sense in the wider context of having better layout primitives.

Groups are useful and provide what such simple layout need to work, bounding rectangles. If its purpose is defied by layouts any other primitive which allow measurements will suffice.

You may replace the implementation FindLayout() with an ImGuiStorage (key->void*) which amortizes.

Good idea. I will do this.

Why aren't g.Layouts/g.LayoutsStack inside ImGuiWindow? (it would tends to allocate more per-window tho, but I am aiming to add an optional local buffer to ImVector template so all that the per-window buffer that are rarely growing a lot can default to use parent memory).

There is no particular reason why these vectors are in the context itself. I had to put them somewhere. Actually moving them to window (together with g.CurrentLayout) can work better and prevent stealing layout when creating child windows. Should I move them to window itself or DC?

If one can't reopen a layout twice a frame, would it make sense to have a single Items array shared by all layouts, to reduce the number of heap allocations? (It may complexity code, right now I'm just asking to understand the reasoning behind data structures)

Layouts are keeping local item arrays to preserve more state between frames. If I use ten layouts one after another and add new item in first one all ten will be invalidated and need recalculation. When keeping items locally only first one need to be rebuild, remaining nine will not change. (Note that change of position of layout does not invalidate it, only change of size). Place for fixed number of items may be allocated together with ImGuiLayout structure and dynamic allocation will kick in only for more complex usages. That would be possible with buffer for ImVector you mention earlier. It isn't obvious in for what usage code should be optimized. Allocations are performed on first pass, then memory is reused. I should add means of removing cached layouts or marking is as free for reuse.

Maybe we should see how layouts behave in real code and optimize them based on profiling later? I mean optimizations which are biased to some kind of usage.

Summary of to do:

  • Use ImGuiStorage instead ImVector for Layouts
  • Move Layouts, LayoutsStack and CurrentLayout to ImGuiWindow
  • Allow for reuse old layouts (this list may actually be in ImGuiContext?)

@ocornut
Copy link
Owner

ocornut commented Jul 29, 2016

Layout is recalculated when size of item inside change or it's bounds change. After that it is sitting idle doing nothing.

The overhead is probably not layout calculation but adding items to arrays, touching memory, etc.

Should I move them to window itself or DC?

The misnamed DC is meant to store things that in theory could be ditched if the window isn't alive. In practice it isn't well organized, but you could store it in DC yes.

It isn't obvious in for what usage code should be optimized. Allocations are performed on first pass, then memory is reused. I should add means of removing cached layouts or marking is as free for reuse.

The idea is mostly that touching more random memory (most commonly when chasing heap pointers) generally leads to lower perfs than touching contiguous memory that could fit in same cache line. As for keeping the number of heap allocations down is it mostly keeping a nice hygiene and a nice image but doesn't matter as much as the actual memory usage patterns. Current implementation of FindLayout will touch every layout and few of them are likely to share a same cache-line. FindWindowByName has the same issue but is likely to be called less than FindLayout and it is already called in a context where we are setuping so much stuff we are likely to trash the cache already. Whereas FindLayout is more likely to be called in a general UI loop which might process thousands of elements.

@thedmd
Copy link
Contributor Author

thedmd commented Jul 29, 2016

I got the idea. More local data is, faster code may run. I will think about that and how to minimize number of allocations further.

Ad for FindLayout it share issues with FindWindowByName because it is modeled after it. ImGuiStorage may remove need of dereferencing pointer on every search.

When you talk about introducing local buffer to ImVector is something like small string optimization in std::string? Have place for a few elements and allocate if capacity limit is reached?

@ocornut
Copy link
Owner

ocornut commented Jul 29, 2016

When you talk about introducing local buffer to ImVector is something like small string optimization in std::string? Have place for a few elements and allocate if capacity limit is reached?

Yes exactly. Most of the vectors are used for stacks for which we can give an optimal static size that is unlikely to be reach, and only use heap on overflow. Most of those stacks will rarely ever have more than say, 4 values.

@ocornut
Copy link
Owner

ocornut commented Jul 30, 2016

About my patch above:
It is incomplete (and therefore a bit arbitrary) and needs further fixing.
I think we want to make SameLine(x > 0) always relative to group-left or column-left, etc.

So SameLine() with non-zero value could add both GroupOffsetX and ColumnsOffsetX:

    if (pos_x != 0.0f)
    {
        if (spacing_w < 0.0f) spacing_w = 0.0f;
        window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffsetX + window->DC.ColumnsOffsetX;

But at this point is it becoming clear we have a problem where too much is hard-coded, and columns are ignoring group offset, etc. So we could clean that out and store our offset (better: min-max ranges?) within a stack with a single semantic.

(Sorry this is not directly related to your commit, but it will probably conflict a little once fixed)

@ocornut
Copy link
Owner

ocornut commented Jul 30, 2016

I have pushed the half-assed fix in the meantime because it gets us much closer to intended behavior. If anyone was mislead thinking the X value was always window-relative (unlikely to be a problem, but it could) it is better to correct it sooner than later. Full cleanup to make group/columns simpler and recursive will have to wait a little.

@thedmd
Copy link
Contributor Author

thedmd commented Aug 5, 2016

I rebased source to master. First commit was cleaned up and still vectorize few variables like Indent.

Rest remain unchanged.

@thedmd
Copy link
Contributor Author

thedmd commented Aug 5, 2016

This can now be done thanks to using layouts and #768. Enjoy!

Nodes are relaying heavily on layouts.

Notice that links (bezier curves) can be selected too.

node_create_fade_3
You can spot minor bugs on this animation if you watch carefully (all are fixed by now).

…them.

This change is an bootstrap for working with layouts as we need to make cursor move horizontally and vertically. Keeping only X coordinate prevent correct measurements.
…cal layout

API with springs and inner item alignment.
…gn may be override for single layout scope. Alignment is not inherited by nested layouts.

Add SuspendLayout()/ResumeLayout() to restore original ImGui positioning inside layout. This make it possible to create a regular group or display more complicated controls like Combo.
… applied until layout tree became stable for first time. Without that weird visual artifact may be spoted when layout aligned to center grow to full size and actually overshot parent layout for one frame. There is no legitimate way in ImGui to move widgets after they are drawn, what is actually required to fix this issue for good. With 'IsStable' at least initial experience isn't weird.
@malamanteau
Copy link

Wow is that node graph/patch panel example code available somewhere? I would love to try it out!

@ocornut
Copy link
Owner

ocornut commented Sep 8, 2016

@retrop Not sure about this specific one, but there's different variants/discussions in #306

@thedmd
Copy link
Contributor Author

thedmd commented Sep 26, 2016

PR was recreated due to lost references after changing fork origin. That was not my intention, sorry.
Updated PR.

@thedmd thedmd closed this Sep 26, 2016
mgerhardy added a commit to vengi-voxel/vengi that referenced this pull request Mar 8, 2017
@zwcloud
Copy link

zwcloud commented Apr 14, 2017

@thedmd Do you implement ImGui::BeginVertical and others in only one frame?

The implementation of Unity3D's IMGUI uses two frames: one for building the stack, another one for calculating positions and sizes. And I'm wondering if that can be achieved in only one frame.

@thedmd
Copy link
Contributor Author

thedmd commented Apr 15, 2017

Short answer is no, I did not. Initially springs lengths are zero and they are calculated at the end of the frame to be used in next one. Non trivial layouts cannot be done in one pass as far as I know.

I see a possibility now to handle to correct widgets placement on screen in very same frame. Logic will follow in next one. This require adding transformation stack to ImDrawList. However, tempering with draw list can introduce problems for custom draw calls and draws that depends on absolute positions on screen (I couldn't think of any in ImGui). I did not used this for layouts yet.

@zwcloud
Copy link

zwcloud commented May 6, 2017

@thedmd Is it possible to remove the first id paramter from BeginHorizontal and BeginVertical? I hope it could be. But since the id is used to find the layout in the stack in current implementation, it seems to be required. Any ideas?

@thedmd
Copy link
Contributor Author

thedmd commented May 6, 2017

Yes, and no. Yes, it is possible but API will be very easy to use incorrectly. Actually it is necessary to make unique ID only for top level layout, all nested layouts will be isolated because ID acts like scope in C++.

From the top of my head, you can define macros:

# define BeginHorizontal2() ImGui::BeginHorizontal(#__LINE__)
# define BeginVertical2() ImGui::BeginHorizontal(#__LINE__)

And use ImGui::PushID()/ImGui::PopID() to generate unique scopes. But this is overkill IMHO.

In reality you need unique ID only for top level layout, as I stated before. Like:

ImGui::BeginVertical(&m_MyItem);
    ImGui::BeginHorizontal("header");
        ImGui::Text("+ %s +", m_MyItem.Name);
        ImGui::Spring(1.0f);
    ImGui::EndHorizontal();
    ImGui::BeginHorizontal("content");
        ImGui::Spring(1.0f);
        ImGui::Text("%s", m_MyItem.Value);
        ImGui::Spring(1.0f);
    ImGui::EndHorizontal();
ImGui::EndVertical();

You can also use constant with top level and write ImGui::BeginVertical("item"); instead of ImGui::BeginVertical(&m_MyItem);. But in this case you need to remember about calling ImGui::PushID()/ImGui::PopID() before invoking layout code.

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

Successfully merging this pull request may close these issues.

4 participants