Search Unity

Layout that I would like to see

Discussion in 'UGUI & TextMesh Pro' started by PrefabEvolution, Aug 31, 2014.

  1. PrefabEvolution

    PrefabEvolution

    Joined:
    Mar 27, 2014
    Posts:
    225
    Play with Unity 4.6. The most annoying thing is layout system.
    1. I can't disable it if i would like to.
    2. I can't make my widget to have absolute left offset and relative right offset(without extra game objects).

    When i working on layout system for my project i think a lot about how to implement it. And what i got now:

    LayoutObject - component that contains information about size of the object(also min/max limits of the object), padding, and references to left/top/right/bottom anchors.
    LayoutAnchor - component that have type(enum {Left, Right, Top, Bottom, MiddleVertical, MiddleHorizontal}), value(offset) and valueMode(enum {absolute, relative}) + methods to calculate is final position and list of objects to notify when parent layoutObject change size or position.

    When LayoutObject change size or position its notify all LayoutObjects that is linked to its anchors. Many LayoutObjects can be linked to one anchor and its doesn't know about context where its appear. Its right to do so because child GUI element should't know about how they should behave inside parent element, only parent GUI element can change layout by changing anchor values. And i think its SOLID way to layout.

    In Unity 4.6 i can bind many elements to one "anchor" but i have to create some extra GameObject(+1 GameObject, +1 RectTransform with lot of properties) that will increase runtime overhead, asset size, and loading time...
     
  2. Kylotan

    Kylotan

    Joined:
    Feb 17, 2011
    Posts:
    212
    It's not clear to me exactly what your problem is. When you talking about absolute vs relative, I assume you mean in the HTML/CSS sense, in which case 'absolute' just means 'relative to the screen'. So you'd just make it a top-level child of the canvas and your position is absolute. In CSS, absolute-left and relative-right would basically be the same as just moving the right-hand side in a bit; then when the viewport resizes, the right hand side will also move to maintain a fixed distance from that edge.

    What you have as a LayoutObject, is basically the combination of a RectTransform plus a LayoutElement component, which you can use to set the limits of the element. There's no specific 'max size', but from what I understand, if you set the preferred size to what you want as the maximum, then set flexible size to zero, the preferred size becomes the max size.

    I don't understand your 3rd paragraph about how you think layout should work, but I do think that the new Unity system has some stuff that attempts to handle this, ie. the AutoLayout functionality. Unfortunately, the documentation for this is still very incomplete and the overlapping interaction between AutoLayout and the RectTransform/Anchor/Pivot stuff is VERY confusing, because the Layout components override some parts of the RectTransform but not all. I hope Unity spend some time on this part of the functionality because a lot of us will need this control over layout and right now it's tricky (if not impossible) to achieve everything that could easily be done with an HTML-style layout.
     
  3. PrefabEvolution

    PrefabEvolution

    Joined:
    Mar 27, 2014
    Posts:
    225
    For me is biggest problem that i can't disable anchors. Every UI element must have RectTransform, but RectTransform is to heavy and made me to use its anchors. RectTransform should only have a size property(ref. SRP) + properties inherited from Transform. And i think is a big architect issue of uGUI.
     
  4. Kylotan

    Kylotan

    Joined:
    Feb 17, 2011
    Posts:
    212
    I agree that RectTransform is quite heavy and complex. I think it would be good if the anchors were split out somehow - especially since LayoutGroups seem to override and ignore some of the anchors.

    But, if I understand things correctly, you could always just set every single anchor to Top Left (or Bottom Left, depending on how you want to handle coordinates), and that gives you the ability to position things however you want, correct? All you'd need then is a simple interface to the width and height. (Someone else posted that you need to edit 'anchoredPosition', though what coordinate system that uses, I do not know.)
     
  5. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    It corresponds to the Pos X and Pos Y you see in the Inspector when the anchors are together. It's the position of the pivot relative to the anchors. Positive X is to the right and positive Y is up.
     
  6. Kylotan

    Kylotan

    Joined:
    Feb 17, 2011
    Posts:
    212
    Is there anywhere that is the position relative to the top left of the element? 99% of the time I don't care at all about the pivot, just the edges, but it seems that I need to know exactly where it is in order for any positioning code to make sense.
     
  7. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    Here's a screenshot of the Scripting Reference page for RectTransform. It shows what is and isn't available.

     

    Attached Files:

  8. Kylotan

    Kylotan

    Joined:
    Feb 17, 2011
    Posts:
    212
    I seem to be repeating myself here across several topics, but the issue is not that I can't find the RectTransform properties, it's that they're too complex to understand. In a custom layout a common use case (including what I think the original poster wants) is going to be to simply set the X position (relative to a parent) and the width (in pixels), but right now, how to do that is not clear. For example, that list of variables makes me think, "Aha, I can just ignore all the complex stuff and set 'rect' directly' - but that's a read-only property.

    In my opinion, making all this dependent on knowledge of the anchor and pivot positions is unnecessary complexity for these use cases, especially when it's not clear how anchors and pivots are affected by or respected by AutoLayout (if at all).
     
  9. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    The X position of what point in the child, relative to which point in the parent?

    The .rect property is the rectangle relative to the pivot - which is normally inside the rect. So even if you could set it, it would probably not do what you're after.

    So if we created a method called something like this, would that help?

    SetStaticRectRelativeToLowerLeftCornerOfParent (float cornerX, float cornerY, float width, float height)
     
  10. JAKJ

    JAKJ

    Joined:
    Aug 17, 2014
    Posts:
    185
    Actually yeah, that would be an awesome method to have. The anchor/pivot stuff works great, but sometimes you also want to just do it by hand.
     
  11. Kylotan

    Kylotan

    Joined:
    Feb 17, 2011
    Posts:
    212
    Normally it would be the X position of the left of the child, relative to the left of the parent. (Of course, when I say 'normally', maybe I mean 'in Western culture' since we write left to right and the first axis in our mathematical coordinate system usually increases from left to right too.)

    But I appreciate that sometimes you'll want to do something else - eg. a Horizontal Layout where the objects are right-aligned, so you might want to set the right edge of the child relative to the right edge of the parent.

    I understand that the anchors let you do all of this with one elegant system, and it works great from the editor! It's just a bit brain-melting when I try and work out how to achieve the same results from script. :)

    Maybe. :) Although I think I would split it out into 2 functions something like these:

    // Resizes the element to the given dimensions, providing no other component is driving these values
    void SetAbsoluteDimensions(float width, float weight)

    // Repositions the element so that its specified edge is the given offset away from the specified edge of the parent,
    // providing no other component is driving these values

    enum RectTransformEdge { TOP, LEFT, BOTTOM, RIGHT };
    void SetRelativePosition(RectTransformEdge childEdge, float offset, RectTransformEdge parentEdge);


    I'd have them as separate functions so that if the size was already calculated (eg. ContentSizeFitter?) we can just query that and iterate accordingly. eg.:

    //pseudocode for a left-aligned horizontal layout group
    int xPosition = 0;
    foreach (Element e in childElements)
    {
    xPosition += paddingLeft;
    e.rectTransform.SetRelativePosition(LEFT, xPosition, LEFT);
    // This next line assumes we have enough room for preferredWidth; in real code
    // I expect there is an 'actual' width, calculated by the first
    // pass of the layout system. Not sure if that has to be done by
    // the custom layout group or not.
    xPosition += e.rectTransform.preferredWidth;
    xPosition += paddingRight;
    xPosition += spacing;
    }


    A right-aligned version would swap the LEFT for RIGHT in both cases, switch paddingLeft and paddingRight, and maybe flip the sign of xPosition (though I can see a good argument for it not working that way, as well).


    I am certain that there are already ways to do exactly the above using the existing system, but sadly I can't work them out for myself yet.

    Hope this is helpful in some way.
     
  12. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    There's some interesting thoughts there.

    I'm now leaning towards something like this:

    Code (CSharp):
    1. enum Edge { Left = 0, Right = 1, Top = 2, Bottom = 3 }
    2. SetInsetFromParentEdge (Edge edge, float inset, bool oppositeEdgeStays)
    You could then set size and position like this:

    Code (CSharp):
    1. rect.sizeDelta = new Vector2 (myWidth, myHeight);
    2. rect.SetInsetFromParentEdge (Edge.Left, myIinsetPosX, false);
    3. rect.SetInsetFromParentEdge (Edge.Top, myInsetPosY, false);
    Or you could specify fixed padding compared to the parent rect with this:

    Code (CSharp):
    1. rect.SetInsetFromParentEdge (Edge.Left, myPaddingLeft, true);
    2. rect.SetInsetFromParentEdge (Edge.Top, myPaddingTop, true);
    3. rect.SetInsetFromParentEdge (Edge.Right, myPaddingRight, true);
    4. rect.SetInsetFromParentEdge (Edge.Bottom, myPaddingBottom, true);
    Would that be intuitive to work with?
     
    ippdev likes this.
  13. PrefabEvolution

    PrefabEvolution

    Joined:
    Mar 27, 2014
    Posts:
    225
    Problem is that RectTransform relative relative size property(sizeDelta) but not absolute. For example if you create a button prefab, and put it on scene you can probably see a wrong size. Just because root RectTransform size depends on the context where its appear. When you resize this prefab and apply, other scene instances probably will have a wrong size, because you change buttons size in another context. I don't like to make any dirty workarounds for thing like this, but will have to... To solve this you can probably split RectTransform and give us a way to choose how GUI elements should layout, with or without context.
     
    Last edited: Sep 2, 2014
  14. Kylotan

    Kylotan

    Joined:
    Feb 17, 2011
    Posts:
    212
    Yeah, that looks like it would fit my needs quite well (or at least, the needs that I think I have! I gave up on custom layouts for now.)

    I think so. I was going to ask how the 2 examples would cope with stretching, since the use case for the top would normally want there to be no stretching based on the parent, whereas the use case at the bottom seems to expect stretching. This would be irrelevant if these functions were only called from inside a layout callback, as they'd reflect the current parent state, whatever that was. But it could be confusing if they were used elsewhere. I don't have a good suggestion for that because I don't understand the system well enough yet.
     
  15. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    I have settled on this method:
    Code (CSharp):
    1. public enum Edge { Left = 0, Right = 1, Top = 2, Bottom = 3 }
    2. public void SetInsetAndSizeFromParentEdge (Edge edge, float inset, float size)
    Not specifying the size at the same time as the inset could create too much potential confusion, since if setting the size after the inset, the inset would be changed. If you want to keep the existing size (from e.g. a ContentSizeFitter) you can just pass the current rect.width or rect.height as argument.

    This method is also pretty much what the built-in layout groups need and they are now changed to use this one.
     
    JAKJ, ippdev and Kylotan like this.