unity-flex-ui icon indicating copy to clipboard operation
unity-flex-ui copied to clipboard

Properly integrate `FlexLayout` into UGUI

Open RichardWepnerPD opened this issue 1 year ago • 1 comments
trafficstars

UGUI has certain ways how it does things, and some are not yet considered by FlexLayout or done differently. The problem of this is that it's either unintuitive to use or just unusable in some scenarios.

Aspects that should be supported:

  • ILayoutElement properties (see https://github.com/gilzoide/unity-flex-ui/issues/26)
  • Children of a layout element should be layed out unless they are configured to be ignored (see ILayoutIgnorer)
    • currently, children need to be marked with a FlexLayout or they are ignored i.e. the opposite
  • properties controlled by layout elements should be "marked" as such (you can observe this with the ContentSizeFitter, *LayoutGroup, etc.)

The FlexLayout can already be used in the current state, however it seems like it can only be used on the top level or with another FlexLayout as parent. This prevents it to be used within a ScrollRect or within layout groups e.g. if you want to transition slowly from UGUI layouts to FlexLayout where its useful.

RichardWepnerPD avatar Aug 06 '24 17:08 RichardWepnerPD

Hey @RichardWepnerPD, thanks for the report.

Short answer

I think your issue is valid, and we should add a new component FlexLayoutGroup that is actually an ILayoutGroup and applies the flex layout in all children RectTransforms as you expect, without removing the current implementation.

Long answer:

So, currently FlexLayout being a separate layout system for UGUI, you need to actually add FlexLayout to all nodes that you want layed out using flexbox. This way, you have to opt-in to using flex, instead of it driving all of your RectTransforms and you needing to opt-out with ILayoutIgnorer instead. Of course, this is a debatable design, there are pros and cons for both approaches. I'm really sorry if it wasn't clear in the README in that you need FlexLayout in children for it to work.

Aspects that should be supported:

  • ILayoutElement properties (see #26)

FlexLayout/Yoga already support minimum/maximum/flexible sizes, the only thing missing is preferred size, which I think should be addressed by measure functions (as discussed in #24). In case we start laying out all children RectTransforms with flex, we'll certainly take ILayoutElement into consideration.

Children of a layout element should be layed out unless they are configured to be ignored (see ILayoutIgnorer) currently, children need to be marked with a FlexLayout or they are ignored i.e. the opposite

This is about what I said in the begining, "the opposite" is actually the current design of things. The thing is: every object needs a YGNode attached for Yoga to actually lay it out. There will be times where you'll want to tweak margin/padding for a leaf node and you'll need a FlexLayout component, but won't want to affect its children, so you'd have to add ILayoutIgnorer to all of them. Also implementing a FlexLayout that creates and tracks YGNodes for all its children is quite more complex than the current implementation, although it is surely doable.

On the other hand, I guess most people will want to go with flex all the way and expect that adding a FlexLayout will lay out everything below it, like you are asking here.

Maybe we could have both things, and actually create a new component, something like FlexLayoutGroup, that is actually an ILayoutGroup and does what most UGUI users would expect. Then the current implementation could be renamed to FlexLayoutNode or FlexNode or something like that and keep its existing behaviour, what do you think? This would be a nice way to avoid compatibility breaks as well.

properties controlled by layout elements should be "marked" as such (you can observe this with the ContentSizeFitter, *LayoutGroup, etc.)

FlexLayout already does this, but it only drives children that also have FlexLayout components.

however it seems like it can only be used on the top level or with another FlexLayout as parent

Yes, this is the current design of it, mostly the "with another FlexLayout as parent", as discussed above.

This prevents it to be used within a ScrollRect

ScrollRects already do the right thing when you have a FlexLayout on the Content root with auto width or height depending on horizontal vs vertical scroll, as long as its children also have FlexLayouts to be layed out by it. You can check this out in the ScrollView sample scene.

gilzoide avatar Aug 11 '24 12:08 gilzoide

I've tried out a few solutions, you are absolutely correct that trying to handle ygnodes for non-flexlayout children is hell.

But I got some progress with this plan:

  • split FlexLayout into FlexLayoutBase FlexLayout and FlexLeaf
  • FlexLayoutBase handles the YGNode
  • FlexLayout has the child handling and layout logic
  • FlexLeaf mostly just sits there

We can then

  • automatically add FlexLeaf to all ILayoutElement children IIF the children don't have an active ILayoutIgnorer and the config has an automatic leaf flag
  • automatically remove FlexLeaf when not in use, if they have been created automatically
  • Automatic FlexLeaf has HideFlag set to DontSave to avoid creating prefab overrides
  • FlexLeaf can be converted to a FlexLayout with an editor button

The automatic adding is not very dangerous, since adding leaf nodes cannot accidentally connect to separate FlexLayout trees, do not modify any assets. It's also a pretty huge QOL improvement.

It doesn't really help with the ContentSizeFitter question, and it doesn't solve the scroll view problem. I should try using a position:static to make the scroll view work.

However, should FlexLayout become a ILayoutGroup (or another FlexLayoutBase child class), this would make the system mostly compatible with UGUI.

I am worried about a few things:

  • how much care should be given to respecting unity UGUI expectations. For example ILayoutIgnorer is handled strangely, where all ILayoutIgnorers of a component need to return ignoreLayout => true for the layout group to ignore the gameobject, and disabling an ILayoutIgnorer has no effect except delaying canvas redraw.

  • how expensive would it be to have unity send ILayoutGroup update calls to all FlexLayout elements. I'd need to check the LayoutRebuilder source

  • should components like ILayoutController and ILayoutSelfController be handled at all? If we have a FlexLayoutGroup, their API will be called, but only if they are at the root or direct children of our FlexLayoutGroup. That would be a bit weird, but also ok.

EDIT: I did manage to get Yoga to auto-size the root node by simply giving it NaN as the width and height.

Image

lgarczyn avatar May 31 '25 15:05 lgarczyn

However, should FlexLayout become a ILayoutGroup (or another FlexLayoutBase child class), this would make the system mostly compatible with UGUI.

My suggestion is we leave FlexLayout exactly the way it is, maybe just inherit a FlexLayoutBase class with common implementation details, and create a new component for Flex UI that is an ILayoutGroup (maybe a "FlexLayoutGroup"). If we just "upgrade" FlexLayout to be an ILayoutGroup we'll break existing projects.

automatically add FlexLeaf to all ILayoutElement children IIF the children don't have an active ILayoutIgnorer and the config has an automatic leaf flag

Thinking how ILayoutGroup usually affects all child UI elements, shouldn't we affect any child RectTransform without a ILayoutIgnorer instead of picking just the ones with ILayoutElement? We can use LayoutUtility to get the layout element values.

how much care should be given to respecting unity UGUI expectations. For example ILayoutIgnorer is handled strangely, where all ILayoutIgnorers of a component need to return ignoreLayout => true for the layout group to ignore the gameobject, and disabling an ILayoutIgnorer has no effect except delaying canvas redraw.

I'd say to try to respect UGUI expectations as much as possible. This ILayoutIgnorer example may be ok to break, I think most people won't have more than 1 ILayoutIgnorer in their objects, but if possible I think we should respect them as well. If the expectations we break are well known, we can also document them for people to be aware.

how expensive would it be to have unity send ILayoutGroup update calls to all FlexLayout elements. I'd need to check the LayoutRebuilder source

Don't worry too much about this. I think being compatible with UGUI matters more in this case. If we have separate components for FlexLayout only vs a Flex ILayoutGroup, people can use the ILayoutGroup more sparingly and alleviate the cost of rebuilding UGUI layout if it ends up being too costly. Yoga will likely avoid recaltulating layouts that haven't changed, so I'd say we worry about that later, after it's implemented.

should components like ILayoutController and ILayoutSelfController be handled at all?

I think so, yeah, respect as much of the UGUI as possible.

If we have a FlexLayoutGroup, their API will be called, but only if they are at the root or direct children of our FlexLayoutGroup. That would be a bit weird, but also ok.

Not sure what you mean by that.

EDIT: I did manage to get Yoga to auto-size the root node by simply giving it NaN as the width and height.

Nice. I really think this should be optional, though. It may make sense to fix the root layout available size by UGUI means (maybe the root layout is child of another ILayoutGroup that drives its rect). It's usually fine to have the root layout size fixed, then a single child that's automatically sized, then this child is the one that contains the other UI elements. This is what the ScrollView sample scene does.

gilzoide avatar Jun 08 '25 15:06 gilzoide

My suggestion is we leave FlexLayout exactly the way it is, maybe just inherit a FlexLayoutBase class with common implementation details, and create a new component for Flex UI that is an ILayoutGroup (maybe a "FlexLayoutGroup"). If we just "upgrade" FlexLayout to be an ILayoutGroup we'll break existing projects.

Agreed

Thinking how ILayoutGroup usually affects all child UI elements, shouldn't we affect any child RectTransform without a ILayoutIgnorer instead of picking just the ones with ILayoutElement? We can use LayoutUtility to get the layout element values.

It actually doesn't affect all elements. If something doesn't have ilayoutelement (like images or text), I think the groups have no effect at all. Will need to double check.

Nice. I really think this should be optional, though. It may make sense to fix the root layout available size by UGUI means (maybe the root layout is child of another ILayoutGroup that drives its rect). It's usually fine to have the root layout size fixed, then a single child that's automatically sized, then this child is the one that contains the other UI elements. This is what the ScrollView sample scene does.

Oh absolutely, but if the root size is auto, I feel like the container should be autosized as well, instead of the current behavior of doing nothing

lgarczyn avatar Jun 09 '25 06:06 lgarczyn

but if the root size is auto, I feel like the container should be autosized as well, instead of the current behavior of doing nothing

Hmm, ok. I guess we could do that, maybe just avoid setting the layout of the root when width and height use the Undefined unit. This could even be done independently from the better UGUI integration.

gilzoide avatar Jun 09 '25 15:06 gilzoide