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

Layout using CCSS principles #268

Open
dumblob opened this issue Jul 9, 2015 · 18 comments
Open

Layout using CCSS principles #268

dumblob opened this issue Jul 9, 2015 · 18 comments
Labels

Comments

@dumblob
Copy link

dumblob commented Jul 9, 2015

After reading all the issues regarding layout, I came to conclusion, that nobody assumed a possibility of defining the layout using some of the ideas of Constraint Cascading Style Sheets, which is used in some form e.g. in Cocoa. There are several other open source projects dealing with this principle:

Do you think some support for CCSS could be added to ImGui?

@hypernewbie
Copy link

I think we should restrict imgui from unnecessary bloat.

@emoon
Copy link
Contributor

emoon commented Jul 9, 2015

+1 If something like this is made it should be layered outside on top of ImGui (like some contrib lib or something similar) I (personally) would like to avoid it inside core ImGui.

@ocornut
Copy link
Owner

ocornut commented Jul 9, 2015

I haven't looked the link yet so it's hard to tell. Better layout features are desirable but I don't know what form they'll take yet. It certainly wouldn't hurt to study those existing techniques and see what can be borrowed from them.

@emoon
Copy link
Contributor

emoon commented Jul 9, 2015

Agreed. I just don't want there to be a requirement of some CSS 'script' to make the layout but ideas are of course good to use.

@dumblob
Copy link
Author

dumblob commented Jul 9, 2015

Yes, that's what I meant - to get inspiration about how to very easily (in very few lines of code) specify rich layout compatible with many different screen/window sizes and ratios and possibly implement (some of) these ideas in ImGui.

Btw, the purpose of this post is to make a rock-solid roadmap for layouting in ImGui, so you really can sleep peacefully without waking up with an idea "quickly, I have to fix this bug and implement this request in ImGui". This should make you more relaxed as you described in #259 .

@emoon
Copy link
Contributor

emoon commented Jul 9, 2015

👍

@phicore
Copy link

phicore commented Jul 9, 2015

Here is one of such contraints solver in c++

https://github.com/Nocte-/rhea

@dumblob
Copy link
Author

dumblob commented Aug 9, 2015

Nice list of good existing solutions implementing the Cassowary algorithm or similar can be found on http://overconstrained.io/ .

@ocornut
Copy link
Owner

ocornut commented Aug 9, 2015

The main problem with those techniques is that they are not suited to the immediate layout we're doing at the moment. Right now we basically have no information of the preceding and upcoming widgets when laying out something. A few parts of ImGui are relying on passing data across frames but it's more occasional than generalized and it has its shortcomings.

I'm not even sure at which sort of granularity you would like to use that sort of constraints. Perhaps describe one or more specific example pertaining to ImGui use and we can see how to work from that.

The immediate use case I can think of are columns which really needs to be improved (if only for their initial width). Their declaration could use some sort of constraint system.

@dumblob
Copy link
Author

dumblob commented Aug 9, 2015

Right now we basically have no information of the preceding and upcoming widgets when laying out something.

Yes, this is an issue for fully constraint-based layouting. I don't see though any obstacles for use in smaller parts or containers (e.g. the columns you mentioned).

few parts of ImGui are relying on passing data across frames but it's more occasional than generalized and it has its shortcomings.

Relying on passing data across frames would be only an optimization (introducing a new whole bunch of problems dealing with invalidation) as Cassowary should be run for every frame (this should be the very basic idea of immediate mode GUIs).

I'm not even sure at which sort of granularity you would like to use that sort of constraints. Perhaps describe one or more specific example pertaining to ImGui use and we can see how to work from that.

Regarding some example, I envision something like having a list of constraints for each widget. This list would be passed to overloaded widget functions instead of fixed sizes. These overloaded widget functions might be thin wrappers over the current low-level widget functions. Each widget function wrapper would connect the list of constraints to the global ImGui context object and once the end of container (or the whole frame) is reached, Cassowary would be automatically run and the computed sizes finally passed to the low-level (original) functions and these can do what's necessary. The issue with this approach is, that it defers the execution, which won't probably work well if combined with low-level widget functions (I didn't check internals of ImGui, so I can't reason about this).

@dumblob
Copy link
Author

dumblob commented Aug 9, 2015

Feel free to try Cassowary in action on http://cacaodev.github.io/Autolayout/ConstraintEditor/ .

@ocornut
Copy link
Owner

ocornut commented Aug 9, 2015

The issue with this approach is, that it defers the execution, which won't probably work well if combined with low-level widget functions

That's the entire point of ImGui to not defer the execution. The API only makes sense because the execution is immediate, and the execution requires to know the position/size of widgets. If the execution isn't immediate all the API have to be changed and you don't have an ImGui anymore.

The way Unity old IMGUI and other implementations often solved this for example is to use a multi-pass approach (their OnGUI function is called multiple times). It help solving that sort of problem very easily but it adds severe restriction on the freedom to use the gui system. Suddenly you can't just call ui stuff anywhere in your code, it has to be in a spot that can be passed over multiple times and that affects architecture.

The reason for passing data across frames would be so that frame N+1 can use the computed result from frame N, which makes sense for stableish form of layout. It's a sort of workaround defacto multi-pass. This is what I am doing e.g. for menus, on their frame 0 they are calculating size and being hidden, and only being displayed from frame 1.

So what I am envisioning is a system that store those data across frames and is designed to be flexible enough that it does the right job in the typical case of having a stable layout but doesn't break badly when the layout changes. That system could also be expressed as user-side helper to allow the user feeding data priorhand.

@dumblob
Copy link
Author

dumblob commented Aug 9, 2015

@ocornut thank you for the explanation of some internals - the N+1 "hack" is a small surprise for me ;). Anyway, there is another approach:

  1. Specify the full constraint-based layout beforehand using some unique user IDs for each widget.
  2. Then running Cassowary and saving the computed sizes and places to some structure.
  3. And first then manually placing the widgets at positions available in the computed structure.

This is enough independent from widget functions and allows the programmer to decide whether to use the computed position and size or not. The first step is the only place where ImGui can interact with the layouting system and help the programmer. ImGui can e.g. provide a minimal set of default/recommended constraints for each such widget, so that one doesn't have to manually come up with sane constraints e.g. for minimal size of a radio button.

Hm, this sounds really interesting to me - I've never assumed, that I could "correct" or somehow adjust or completely reuse (for any purpose) internally computed sizes and positions. That opens a whole new dimension of possibilities. Thanks guys for this discussion!

@ocornut
Copy link
Owner

ocornut commented Aug 9, 2015

Do you have a more concrete example in mind? I'm not sure to understand how steps 1 and 3 would work and the how extra code required would be expressed, what sort of extra code or potential code duplication it is entail.

Here's one scenario of a "simple" thing that ImGui cannot do naturally at the moment:
checkboxes
Ideally what I would want here (or at least, that one of the possible thing I could want) is to decide of the width of the first "column" as the max() of width of all items in that column.

That code is using hardcoded X position (using SameLine, which can used as simplified columns when no clipping is required. That measurement is relatively easy to do on the user size but rather cumbersome and it is the sort of thing you would wish the UI can do for you. How would that fit in your steps? Happy to hear about other scenarios that are more fitting to use of CCSS.

@dumblob
Copy link
Author

dumblob commented Aug 9, 2015

I'm not sure to understand how steps 1 and 3 would work and the how extra code required would be expressed, what sort of extra code or potential code duplication it is entail.

There shouldn't be any code duplication (except for the corner case described below). Imagine something like the following pseudocode:

// row of 4 buttons with spaces between them and distance from the global frame borders
constraints = {
  {"id00", imgui::default_constraints(IMGUI_BUTTON) + "global.x_left + 10 = self.x_left; global.y_top + 50 = self.y_top" },
  {"id01", imgui::default_constraints(IMGUI_BUTTON) + "id00.x_right + 5 = self.x_left; id00.y_top = self.y_top" },
  {"id02", imgui::default_constraints(IMGUI_BUTTON) + "id01.x_right + 5 = self.x_left; id00.y_top = self.y_top" },
  {"id03", imgui::default_constraints(IMGUI_BUTTON) + "id01.x_right + 5 = self.x_left; id00.y_top = self.y_top; global.x_right - 10 = self.x_right" },
}
geometry = imgui::solve(constraints)
imgui::button(geometry["id00"], ...)
imgui::button(geometry["id01"], ...)
imgui::button(geometry["id02"], ...)
imgui::button(geometry["id03"], ...)

Ideally what I would want here (or at least, that one of the possible thing I could want) is to decide of the width of the first "column" as the max() of width of all items in that column.

I've deliberately avoided an example with any widget having variable minimum/maximum width/height as part of the constraint. In practice this is only text under certain weird conditions (I can't think of any good example except for an imaginary GUI version of the UNIX ls command 😄). Minimums and maximums must be, of course, known in advance. Maximums are though not as common as minimums.

It may sound like an issue, but actually it doesn't matter at all, because no widgets are sized according to the text dimensions as the text has very different length every single time (e.g. due to translations and/or just different system font settings and/or different font and/or different DPI and/or whatever) and nobody wants UI changing its geometry based on some changes in text (if you don't happen to be a designer, then just ask any to get a proof, that changing geometry is very unpleasant for the end user).

If you insist you need it, you can render the text to some temporary buffer tb to get the size and either throw away tb and re-render the text again to the final buffer or not throw tb away and instead of re-rendering just copy the contents of tb over the final buffer. There might be an overloaded function imgui::default_constraints(int widget_type, char *text_to_render) specialized for text defaults.

In the case you described, I'd choose some ratio of available width as the column size.

Regarding any other scenario, imagine you wanted to rewrite the GitHub pages layout with all its dynamics and properties :)

@ocornut
Copy link
Owner

ocornut commented Aug 9, 2015

I don't see the value of this specific example seeing it can be done perfectly without constraint and in a less cumbersome way. I would have imagined that the purpose of constraint that to solve more interesting layout cases, here we don't have dependencies between the elements.

You can express this with something like width = (right.x - pos.x) / 4, more or less (need to consider padding, Etc.), which would suggest that more specific helpers would be useful.

Now, I can envision the value of this and I think we will evolve toward a similar structure, with tools to calculate the position/size of widget prior-hand. Those tools can specific helpers or a more generic constraint solver. At this point it becomes a matter of syntax but my intuition is that we can have specialized vertical/horizontal layout objects and do most of this simpler and faster.

I'd be interested in more convincing examples. (not suggesting this is bad in any way but I need more example to enrich the design process).

(

because no widgets are sized according to the text dimensions as the text has very different length every single time (e.g. due to translations and/or just different system font settings and/or different font and/or different DPI and/or whatever)

That would apply to most UI but I don't think it applies to my intended typical use of ImGui which is to make very fast tools and rely on as many assumption as possible to make them easily. They are most often tools that are not-localized, will all strings inline, a known font. The user would often want to try minimizing screen real-estate because when dealing with a game engine you are dealing with large amount of data and options. so that sort of pattern to pack the check-box makes sense.
We have functions like CalcTextSize() to calculate width/height that are vastly more efficient than the cost of rendering them.
)

Are you using ImGui yourself?

@dumblob
Copy link
Author

dumblob commented Nov 16, 2016

Hi @ocornut, let me apologize for the delay (life's hard).

That would apply to most UI but I don't think it applies to my intended typical use of ImGui which is to make very fast tools and rely on as many assumption as possible to make them easily. They are most often tools that are not-localized, will all strings inline, a known font. The user would often want to try minimizing screen real-estate because when dealing with a game engine you are dealing with large amount of data and options. so that sort of pattern to pack the check-box makes sense.

According to screenshots of ImGui in the wild, I'm totally certain, that your "intended typical use of ImGui" does unfortunately not match the typical ImGui use of others (which is rather closer to the task I mentioned in #268 (comment) , i.e. "rewrite the GitHub web pages UI layout with all its dynamics and properties").

Anyway, there is a neat, tiny, and fast single-header library for Cassowary - https://github.com/starwing/amoeba . Feel free to add support for it to get both DPI agnostic and physical screen size/ratio agnostic layouts.

Are you using ImGui yourself?

Actually Nuklear fits my use cases better.

@ernestchanjq
Copy link

Is that mean there is declarative layout support? If anyone create another QT from imgui to support like CSS flexbox using over Facebook iOS and their so call cross platform layout engine: https://facebook.github.io/yoga/

Hopefully there is one project using technology that really cross platform, for now the best might be QT, but the licensing...

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

No branches or pull requests

6 participants