Search Unity

Rect Transform Size Limiter

Discussion in 'UGUI & TextMesh Pro' started by Democide, Jan 29, 2019.

  1. Democide

    Democide

    Joined:
    Jan 29, 2013
    Posts:
    315
    I needed something that makes sure a rect transform with a ContentSizeFitter doesn't exceed a certain size.

    So I put something together quickly. Make sure to slap it on as a component after the content size fitter. With X or Y set to 0 that axis isn't limited.

    https://bitbucket.org/snippets/Democritus/5ex7n4
     
  2. i0plus_developer

    i0plus_developer

    Joined:
    Oct 20, 2018
    Posts:
    25
    Thank you!
    That's exactly what we need!
     
  3. Straafe

    Straafe

    Joined:
    Oct 15, 2012
    Posts:
    73
    This is exactly what I needed as well. After reading countless posts about people saying to just use preferred width with flexible width set to 0 to control max width (which didn't work at all in my case), this solved it perfectly.
     
  4. giggioz

    giggioz

    Joined:
    May 11, 2017
    Posts:
    52
    This is pure gold, thank you.

    I slightly modified the code to support also a minSize (you can probably achieve this with Layout element but this way you have only one script to manage everything).

    It's just a dumb extension of your code, so, please, take a look at it if you notice something wrong.

    Thanks again.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.EventSystems;
    5. using UnityEngine.UI;
    6.  
    7. [ExecuteInEditMode]
    8. public class RectSizeLimiter : UIBehaviour, ILayoutSelfController
    9. {
    10.  
    11.     public RectTransform rectTransform;
    12.  
    13.     [SerializeField]
    14.     protected Vector2 m_maxSize = Vector2.zero;
    15.  
    16.     [SerializeField]
    17.     protected Vector2 m_minSize = Vector2.zero;
    18.  
    19.     public Vector2 maxSize
    20.     {
    21.         get { return m_maxSize; }
    22.         set
    23.         {
    24.             if (m_maxSize != value)
    25.             {
    26.                 m_maxSize = value;
    27.                 SetDirty();
    28.             }
    29.         }
    30.     }
    31.  
    32.     public Vector2 minSize
    33.     {
    34.         get { return m_minSize; }
    35.         set
    36.         {
    37.             if (m_minSize != value)
    38.             {
    39.                 m_minSize = value;
    40.                 SetDirty();
    41.             }
    42.         }
    43.     }
    44.  
    45.     private DrivenRectTransformTracker m_Tracker;
    46.  
    47.     protected override void OnEnable()
    48.     {
    49.         base.OnEnable();
    50.         SetDirty();
    51.     }
    52.  
    53.     protected override void OnDisable()
    54.     {
    55.         m_Tracker.Clear();
    56.         LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
    57.         base.OnDisable();
    58.     }
    59.  
    60.     protected void SetDirty()
    61.     {
    62.         if (!IsActive())
    63.             return;
    64.  
    65.         LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
    66.     }
    67.  
    68.     public void SetLayoutHorizontal()
    69.     {
    70.         if (m_maxSize.x > 0f && rectTransform.rect.width > m_maxSize.x)
    71.         {
    72.             rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, maxSize.x);
    73.             m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaX);
    74.         }
    75.  
    76.         if (m_minSize.x > 0f && rectTransform.rect.width < m_minSize.x)
    77.         {
    78.             rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, minSize.x);
    79.             m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaX);
    80.         }
    81.  
    82.     }
    83.  
    84.     public void SetLayoutVertical()
    85.     {
    86.         if (m_maxSize.y > 0f && rectTransform.rect.height > m_maxSize.y)
    87.         {
    88.             rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, maxSize.y);
    89.             m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaY);
    90.         }
    91.  
    92.         if (m_minSize.y > 0f && rectTransform.rect.height < m_minSize.y)
    93.         {
    94.             rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, minSize.y);
    95.             m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaY);
    96.         }
    97.  
    98.     }
    99.  
    100. #if UNITY_EDITOR
    101.     protected override void OnValidate() {
    102.         base.OnValidate();
    103.         SetDirty();
    104.     }
    105. #endif
    106.  
    107. }
     
  5. Wekthor

    Wekthor

    Joined:
    Apr 30, 2011
    Posts:
    165
    @Democide Thank you, finally a reasonable solution to a simple problem.
     
  6. IamJohnny45

    IamJohnny45

    Joined:
    Nov 6, 2019
    Posts:
    2
    @Democide Works perfectly. Thank you very much! I finally I have a text box that shrinks and expands to the text size, but forces a wrap of text when it reaches max width. Unity should absolutely add max width/height to the Layout Element.
     
    worldofvrgmbh and Upian like this.
  7. Ultroman

    Ultroman

    Joined:
    Mar 10, 2014
    Posts:
    110
    This is amazing! I've only wanted this for 7 years XD

    I tried using this to create a textbox with an auto-sizing background image, but ran into the same type of staggered size updates on the parent with the background image, so I added an optional reference to a parent Transform, which makes the parent also automatically fit the text element.

    You'll still have a problem with any other parent that wants to react to the size of the text element, so this still isn't a fix for all use-cases, but it's a great start!

    Thanks a whole lot, Democide and giggioz!

    Usage notes:
    To make a textbox, this script is supposed to sit on the GameObject with the TextMeshPRO component on it, after you have added a Content Size Fitter to it (set both its settings to Preferred Size). Do not try to use Unity's normal Text components; they do not work properly, especially when it comes to tight fits, which make them stop showing due to floating point errors or something.

    You also have to drag a reference to the child that the script is sitting on into the "Rect Transform" inspector field. It doesn't find it automatically, to make it so the script doesn't necessarily have to sit on the same GameObject that it controls RectTransforms for.

    If you wish to have another RectTransform (usually the parent) also be sized along with the main "Rect Transform", you can also add a reference to the "Parent Rect Transform" inspector field. Set the parent to stretch both horizontally and vertically (click the Anchor Presets button, hold Alt and press the button in the bottom-right with the two blue arrows). The parent does not need any other layout components for this to work.

    In order to get margins, don't use negative values in top/bottom/left/right on the child (text) RectTransform, but instead set the margins on the TextMeshPRO component itself, under Extra Settings. That way the parent can still be positioned properly using anchors.

    Code
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.UI;
    4.  
    5. [ExecuteInEditMode]
    6. public class RectSizeLimiter : UIBehaviour, ILayoutSelfController
    7. {
    8.  
    9.     public RectTransform rectTransform;
    10.     public RectTransform parentRectTransform;
    11.  
    12.     [SerializeField]
    13.     protected Vector2 m_maxSize = Vector2.zero;
    14.  
    15.     [SerializeField]
    16.     protected Vector2 m_minSize = Vector2.zero;
    17.  
    18.     public Vector2 maxSize
    19.     {
    20.         get { return m_maxSize; }
    21.         set
    22.         {
    23.             if(m_maxSize != value)
    24.             {
    25.                 m_maxSize = value;
    26.                 SetDirty();
    27.             }
    28.         }
    29.     }
    30.  
    31.     public Vector2 minSize
    32.     {
    33.         get { return m_minSize; }
    34.         set
    35.         {
    36.             if(m_minSize != value)
    37.             {
    38.                 m_minSize = value;
    39.                 SetDirty();
    40.             }
    41.         }
    42.     }
    43.  
    44.     private DrivenRectTransformTracker m_Tracker;
    45.  
    46.     protected override void OnEnable()
    47.     {
    48.         base.OnEnable();
    49.         SetDirty();
    50.     }
    51.  
    52.     protected override void OnDisable()
    53.     {
    54.         m_Tracker.Clear();
    55.         LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
    56.         base.OnDisable();
    57.     }
    58.  
    59.     protected void SetDirty()
    60.     {
    61.         if(!IsActive())
    62.             return;
    63.  
    64.         LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
    65.     }
    66.  
    67.     public void SetLayoutHorizontal()
    68.     {
    69.         if(m_maxSize.x > 0f && rectTransform.rect.width > m_maxSize.x)
    70.         {
    71.             rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, maxSize.x);
    72.             m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaX);
    73.         }
    74.  
    75.         if(m_minSize.x > 0f && rectTransform.rect.width < m_minSize.x)
    76.         {
    77.             rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, minSize.x);
    78.             m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaX);
    79.         }
    80.  
    81.         if (parentRectTransform != null)
    82.         {
    83.             parentRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, rectTransform.sizeDelta.x);
    84.             m_Tracker.Add(this, parentRectTransform, DrivenTransformProperties.SizeDeltaX);
    85.         }
    86.     }
    87.  
    88.     public void SetLayoutVertical()
    89.     {
    90.         if(m_maxSize.y > 0f && rectTransform.rect.height > m_maxSize.y)
    91.         {
    92.             rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, maxSize.y);
    93.             m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaY);
    94.         }
    95.  
    96.         if(m_minSize.y > 0f && rectTransform.rect.height < m_minSize.y)
    97.         {
    98.             rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, minSize.y);
    99.             m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaY);
    100.         }
    101.  
    102.         if(parentRectTransform != null)
    103.         {
    104.             parentRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, rectTransform.sizeDelta.y);
    105.             m_Tracker.Add(this, parentRectTransform, DrivenTransformProperties.SizeDeltaY);
    106.         }
    107.     }
    108.  
    109. #if UNITY_EDITOR
    110.     protected override void OnValidate()
    111.     {
    112.         base.OnValidate();
    113.         SetDirty();
    114.     }
    115. #endif
    116.  
    117. }
    118.  
     
    Last edited: Sep 21, 2021
  8. huulong

    huulong

    Joined:
    Jul 1, 2013
    Posts:
    224
    Nice! In my case I needed it to work inside a Horizontal Layout with another item, I wanted fully flexible to occupy all the remaining space.

    If the other element's LayoutElement preferred / min size is too low:

    2022-09-23 Rect Size Limiter works but harder to get perfect complementary fill.png

    Unfortunately it doesn't seem possible, but I could at least get a decent result by setting Min Size = Max Size on RectSizeLimiter to avoid being pushed back by the other element, and LayoutElement preferred width on the other element to be high enough.

    Also note that I set the pivot where I wanted the rect to be aligned: here, I set it to the right so the "99" text touches the right edge of the parent rect.

    The risk however is to get overlap.

    2022-09-23 Rect Size Limiter - min=max will enforce correct size but overlap in counterpart.png

    And of course, when the parent size changes it gets even harder to control how the who will evolve relatively to each other.

    I suppose we'd need a replacement for Layout Element itself to support max size, so this can interoperate with other Layout Elements... I've been following https://forum.unity.com/threads/why-doesnt-layout-element-have-max-values.274221/ but it just redirected me here.
     
  9. Ziplock9000

    Ziplock9000

    Joined:
    Jan 26, 2016
    Posts:
    360
    While this does indeed limit the size, it still reports the larger size to parents. So anything more than basic layouts does not work.