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

Columns with groups of widgets #608

Open
MKUltra42 opened this issue Apr 24, 2016 · 7 comments
Open

Columns with groups of widgets #608

MKUltra42 opened this issue Apr 24, 2016 · 7 comments

Comments

@MKUltra42
Copy link

This is probably me just not fully understanding how things work...

I'm trying to work on a property grid type thing. I'd like to use the Columns() feature, because I feel I will get more flexibility for complex types where the value may be something like a curve (I also get to choose to have labels appear on the left-hand side ... not sure if I'm missing a simple config to enable that behavior).

So, trying to expose a 'Position', I'd like the value part of the column to be a widget composed of 3 individual DragFloats with their respective labels (instead of a DragFloat3 without a label):

if (ImGui::CollapsingHeader("Stats", 0, true, true))
{
    ImGui::Columns(2, "statscols", true);
    ImGui::Text("Name");
    ImGui::NextColumn();
    ImGui::Text("Value");
    ImGui::NextColumn();
    ImGui::Separator();
    ImGui::Text("Position");
    ImGui::NextColumn();

    ImGui::PushItemWidth(-1);
    static float v[3] = { 0.0f, 1.0f, 2.0f };
    ImGui::DragFloat("X", &v[0]); ImGui::SameLine();
    ImGui::DragFloat("Y", &v[1]); ImGui::SameLine();
    ImGui::DragFloat("Z", &v[2]);

    ImGui::PopItemWidth();
}

I've been trying to mess around with PushItemWidth() and things like wrapping the DragFloat/SameLine combos into a BeginGroup/EndGroup, but anything I do will usually show the first DragFloat with some clipping on its label (I think because I use ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2,2));).

I guess I need help :/

@ocornut
Copy link
Owner

ocornut commented Apr 24, 2016

Hello,

Have you looked at the Property Editor example in the demo function?
I also have to say that Columns are still probably a bit underwhelming and not as functional in all scenarios as people may want them to be. I have about a million tasks to do related to columns.

(I also get to choose to have labels appear on the left-hand side ... not sure if I'm missing a simple config to enable that behavior).

I realise many people want to be heading straight into that but there's many layout benefits and extra flexibility with having widget on the left. Lots of the layout/alignment features make less sense with widget on the right, and you can't as easily add extra contents to the right of a widget which can be super useful. I have an unfinished stash for #395 to allow various left-right/right-left/ two-lines variants, which is half finished, but I'm so terrified that people want to opt for label-widget as default and totally miss on the benefits that it's not been my priority. Anyway it's not finished.

I'd like the value part of the column to be a widget composed of 3 individual DragFloats with their respective labels (instead of a DragFloat3 without a label):

May I ask why you absolutely want those X/Y/Z labels? (and make the usage experience more complicated)

PushItemWidth(-1) means uses all remaining space.
There's no trivial perfect way to do this. You can look at DragFloatN() and figure out the maths to leave enough space for 3 of your widgets considering the label size. You basically want to do something close to PushItemWidth((avail_width / 3) - label_width - style.InnerSpacing.x); but there's probably a few other subtleties involved. When calling SameLine() you can optionally pass an X position as well which may facilitate your alignment.

Something like:

PushItemWidth(-1); 
float avail_width = CalcItemWidth();
float label_width = CalcTextSize("M").x;
PopItemWidth();
PushItemWidth((avail_width / 3) - label_width - style.InnerSpacing.x);
DragFloat("X", ...);
SameLine(0.0f, 0.0f); // zero spacing
DragFloat("Y", ...);
SameLine(0.0f, 0.0f); // zero spacing
DragFloat("Z", ...);
PopItemWidth();

The exact formula may be closer to what PushMultiItemsWidths() is doing internally.
You can get it to work, it may be a little troublesome especially if you are just getting started with ImGui.

@MKUltra42
Copy link
Author

Thanks for the response! The code definitely works and helped me understand a bit about how ImGui works. I will give right labels a try, because the widgets section in the demo app looked very clean! It's just fighting against what folks want out of a regular .NET-style PropertyGrid.

Regarding the X, Y, Z labels. I wanted to replicate a bit how Unity works and I'm just trying to explore the layout possibilities that ImGui presents. I saw your modification to the ImGui::BeginGroup/EndGroup comment and I do wonder if there is a possibility to create a primitive that treats all nested primitives as a single primitive (in a way 'capture' the current region and layout children against that). That could be very powerful from a layout perspective.

All that being said, please keep up the good work! The last time I looked at ImGuis was when the MollyRocket vids about it came up and @memononen pestered me as he was creating Recast. Things have progressed much further than I ever imagined.

@MKUltra42
Copy link
Author

I guess what I'm saying is: Column() should provide a N-way split of a region and once I move to a column via NextColumn() everything should behave the same as if no Column were specified. (I understand it's still a very 'in-development' type of feature).

ocornut added a commit that referenced this issue Apr 25, 2016
@ocornut
Copy link
Owner

ocornut commented Apr 25, 2016

I saw your modification to the ImGui::BeginGroup/EndGroup comment and I do wonder if there is a possibility to create a primitive that treats all nested primitives as a single primitive (in a way 'capture' the current region and layout children against that). That could be very powerful from a layout perspective.

Could you clarify this? It feels that the language is subtle enough that everybody may be talking about something different. BeginGroup/EndGroup capture the box of the group at the end of it so that it can be used as 1 item for layout purpose.

I guess what I'm saying is: Column() should provide a N-way split of a region and once I move to a column via NextColumn() everything should behave the same as if no Column were specified. (I understand it's still a very 'in-development' type of feature).

Yes, it is roughly what it does but a) it doesn't recurse b) isn't that neat and has many subtleties, some necessary some not. So I need to fix that at some point while catering but lots of other desirable features such as columns headers with reorder/sort button, automatic scrolling, etc. What will happens it is it will turn into a BeginColumns()/EndColumns() and Columns() will be the obsolete function for simpler cases.

@MKUltra42
Copy link
Author

The behavior I was expecting out of BeginGroup/EndGroup was for ...

    ImGui::PushItemWidth((avail_width / 3) - label_width - ImGui::GetStyle().ItemInnerSpacing.x);
    ImGui::DragFloat("X##scale", &scale[0]); ImGui::SameLine();
    ImGui::DragFloat("Y##scale", &scale[1]); ImGui::SameLine();
    ImGui::DragFloat("Z##scale", &scale[2]);
    ImGui::PopItemWidth();

... to become roughly equivalent from a layouting perspective to ...

    ImGui::PushItemWidth(avail_width);
    ImGui::BeginGroup();
      ImGui::DragFloat("X##scale", &scale[0]); ImGui::SameLine();
      ImGui::DragFloat("Y##scale", &scale[1]); ImGui::SameLine();
      ImGui::DragFloat("Z##scale", &scale[2]);
    ImGui::EndGroup();
    ImGui::PopItemWidth();

So, for the entirety of the group to be sized to the ItemWidth that got pushed. Anyways, thanks for taking the time to respond and looking forward to see how things develop!

@ocornut
Copy link
Owner

ocornut commented Apr 26, 2016

Marco, that's unfortunately not easily possible in a single-pass imgui context.
At the time of drawing the first DragFloat() the library has no idea of what is upcoming
.
A)
One way which I think is the saner is to embrace and keep massaging layout helpers where you'd explicitly state you have 3 equally sized elements. In my opinion the vast majority of case can be solved this way and with much more ease.

This what Columns() should in theory do but it has been designed for larger chunks - it needs to be vary of clipping (items getting out of the available bounds), resizing and storing scrollbars, changing of available sizes. So Columns just needs to be redone and perhaps there'll be more helpers. No magic here, somebody just needs to sit down to re-engineer all of it (I wish I could but I have months worth of work on my plate and working on spare time).

Note that you can also just position the x cursor e.g. SameLine() or SetCursorPosX() and be done with it if you know your items aren't getting out of the bounds they have.

The more I think about it the more I want to explicitly split that into Columns() that are full featured (and will be much fuller featured in the future - headers, reordering etc.) and a simple fast low-level version for explicitly that sort of case and then it becomes a smaller problem to solve, as long as it doesn't try to deal with clipping/objects getting out of bounds.

B)
Another approach for more automatic layout would be to introduce a local multi-pass pattern, where some helper macros would turn a scope of code into a 2-pass loops where one can log contents at a given scope and the other pass can decide on sizing/positioning. This would solve more advanced layout problems and could be optional, tho I don't see that happening before an hypothetical ImGui 2.0, would require lots more ground work, whereas massaging the techniques discussed in A) would lead results realistically sooner.

@memononen
Copy link
Contributor

When I was looking for 1-pass(ish) alternatives to html flexbox model, I came up with this:
https://github.com/memononen/milligui/blob/master/example/example3.c#L337

It has two modes layout and divs. Layout basically stacks things after each other, and when something does not fit, it'll go to new line or column (IIRC). It can stack horiz or vert and you can change at which end it stacks on the fly. Divs creates "columns" and each new item goes to a next column (or if vertical, rows). A divs can be specific size or you can say "use the rest" (width = -1), in which case the remaining space is divided between non-specified sizes.

You can put layouts and divs inside each other as you wish to create more complex layouts. The dock layout thing you see in the example is just syntactic sugar for a layout with some specific packing.

The reason I did not quite like this was that you had to specify quite a bit measures yourself. But if you have things like common row height, no problem.

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

3 participants