Skip to content

Instantly share code, notes, and snippets.

@wappenull
Created July 24, 2024 12:19
Show Gist options
  • Save wappenull/6f09af3d2c2635aa74eda3c6987ea432 to your computer and use it in GitHub Desktop.
Save wappenull/6f09af3d2c2635aa74eda3c6987ea432 to your computer and use it in GitHub Desktop.
This is accompany script for my own StackOverflow answer, see comment below for more info.
using UnityEngine.EventSystems;
using UnityEngine;
using UnityEngine.UI;
namespace Wappen.UI
{
/// <summary>
/// Similar to LayoutElement but with limiter.
/// </summary>
[AddComponentMenu( "Layout/Layout Element Filter", 141 )]
[ExecuteInEditMode]
public class LayoutElementFilter : LayoutElementBehaviour
{
public enum FitMode
{
Unconstrained,
MinSize,
PreferredSize
}
[Tooltip("Must implement ILayoutElement.")]
[SerializeField] Component m_SourceElement = default;
[SerializeField] protected FitMode m_HorizontalFit = FitMode.Unconstrained;
[SerializeField] protected FitMode m_VerticalFit = FitMode.Unconstrained;
[Header("Filtering")]
[Tooltip( "Max width." )]
[SerializeField] protected float m_MaxPreferredWidth = 0;
float m_PreferredWidth;
float m_PreferredHeight;
public override float preferredWidth => m_PreferredWidth;
public override float preferredHeight => m_PreferredHeight;
#if UNITY_EDITOR
protected override void OnValidate( )
{
if( m_SourceElement != null && !(m_SourceElement is ILayoutElement) )
{
// When drag and drop, m_SourceElement could get RectTransform instead
// Make sure to reacquire if ILayoutElement is avilable
ILayoutElement nextAvailable = m_SourceElement.GetComponent<ILayoutElement>( );
if( nextAvailable != null && nextAvailable is Component c )
{
Helper.RecordUndo( this, "m_SourceElement route" );
m_SourceElement = c; // Change to that instead
}
else
{
Debug.LogWarning( $"{this.GetDebugPath( )} requires m_SourceElement to be component with ILayoutElement.", this );
}
}
base.OnValidate( );
}
#endif
private void HandleFittingAlongAxis( int axis )
{
FitMode fitting = (axis == 0 ? m_HorizontalFit : m_VerticalFit);
RectTransform.Axis ax = (RectTransform.Axis)axis;
ILayoutElement elm = m_SourceElement as ILayoutElement;
if( fitting == FitMode.Unconstrained || elm == null )
{
if( ax == RectTransform.Axis.Horizontal )
m_PreferredWidth = -1;
else
m_PreferredHeight = -1;
return;
}
// Set size to min or preferred size
float s;
if( fitting == FitMode.MinSize )
{
if( ax == RectTransform.Axis.Horizontal )
s = elm.minWidth;
else
s = elm.minHeight;
//s = LayoutUtility.GetMinSize( r, axis );
}
else
{
if( ax == RectTransform.Axis.Horizontal )
s = elm.preferredWidth;
else
s = elm.preferredHeight;
//s = LayoutUtility.GetPreferredSize( r, axis );
}
// Apply limit
if( axis == 0 && m_MaxPreferredWidth > 0 ) // Horz
s = Mathf.Clamp( s, 0, m_MaxPreferredWidth );
if( ax == RectTransform.Axis.Horizontal )
m_PreferredWidth = s;
else
m_PreferredHeight = s;
}
public override void CalculateLayoutInputHorizontal( )
{
HandleFittingAlongAxis( 0 );
}
public override void CalculateLayoutInputVertical( )
{
HandleFittingAlongAxis( 1 );
}
}
/// <summary>
/// Base class for class that want to control ILayoutElement.
/// </summary>
[RequireComponent( typeof( RectTransform ) )]
public abstract class LayoutElementBehaviour : UIBehaviour, ILayoutElement
{
/* ILayoutElement //////////////////////////////////*/
public virtual float minWidth => -1;
public virtual float preferredWidth => -1;
public virtual float minHeight => -1;
public virtual float preferredHeight => -1;
public virtual float flexibleWidth => -1;
public virtual float flexibleHeight => -1;
[SerializeField] private int m_LayoutPriority = 2;
public int layoutPriority => m_LayoutPriority;
public abstract void CalculateLayoutInputHorizontal( );
public abstract void CalculateLayoutInputVertical( );
/* Unity ///////////////////////////////////////*/
#if UNITY_EDITOR
protected override void OnValidate( )
{
SetDirty( );
}
#endif
/* Internal ///////////////////////////////////////////*/
[System.NonSerialized] RectTransform m_Rect;
protected RectTransform rectTransform
{
get
{
if( m_Rect == null )
m_Rect = GetComponent<RectTransform>( );
return m_Rect;
}
}
/* Dirtying stuff //////////////////////*/
protected override void OnEnable()
{
base.OnEnable();
SetDirty();
}
protected override void OnTransformParentChanged()
{
SetDirty();
}
protected override void OnDisable()
{
SetDirty();
base.OnDisable();
}
protected override void OnDidApplyAnimationProperties()
{
SetDirty();
}
protected override void OnBeforeTransformParentChanged()
{
SetDirty();
}
protected void SetDirty()
{
if (!IsActive())
return;
LayoutRebuilder.MarkLayoutForRebuild(transform as RectTransform);
}
}
}
@wappenull
Copy link
Author

This is for My own answer in StackOverflow answer

What this script does is allow you to pulls other element's width/height and directly override current element's size.

For example, suppose we have HorizontalLayoutGroup and the traditional "Button - TextMeshPro" UI object where you want button to have dynamic size depending on its TMP_Text child inside you would have to insert LayoutSizeFitter in both TMP_Text (first to have text decide its own size) and in Button (to make it fits from its text inside). BUT oh wait, that doesn't work, because Fitter on TMP_Text will blindly use size reported from its Image instead of TMP_Text we want. So it is another rabbit hole to set this up.

I'm not sure if currently (2024), there is official or more correct way to do this but this script was my own custom resolution to this problem.
So instead of that mess in above paragraph, from the freshly created "Button - TextMeshPro" UI object, just insert this script on Button object and set it to "report" the size from child text object. boom done. (HorizontalLayoutGroup also need some setup from default, as seen from screenshot)
image

@wappenull
Copy link
Author

Note: For readers coming from "flowlayoutgroup" StackOverflow question
The example image in first comment used HorizontalLayoutGroup for simplicity and for demonstrating the script.
To get the real "flowlayoutgroup" you are seeking, don't forget to replace to the real "flowlayoutgroup" controller you have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment