Skip to content

Instantly share code, notes, and snippets.

@Malkyne
Last active June 2, 2025 13:17
Show Gist options
  • Save Malkyne/a87b44c6b35c0ffc256f244efabdeb53 to your computer and use it in GitHub Desktop.
Save Malkyne/a87b44c6b35c0ffc256f244efabdeb53 to your computer and use it in GitHub Desktop.
A two-way content-size fitter for Unity UGUI
// Copyright (c) 2025 Hidden Achievement, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
// Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
/// <summary>
/// A modified version of ContentSizeFitter that allows a container to expand in
/// two directions.
/// </summary>
[AddComponentMenu("Layout/Multi-Axis Content Size Fitter")]
[ExecuteInEditMode]
[RequireComponent(typeof(RectTransform))]
public class MultiAxisContentSizeFitter : UIBehaviour, ILayoutSelfController
{
public enum FitMode
{
Unconstrained,
MinSize,
PreferredSize
}
[SerializeField]
protected FitMode m_HorizontalFit = FitMode.Unconstrained;
public FitMode horizontalFit { get { return m_HorizontalFit; } set { if (SetStruct(ref m_HorizontalFit, value)) SetDirty(); } }
[SerializeField]
protected FitMode m_VerticalFit = FitMode.Unconstrained;
public FitMode verticalFit { get { return m_VerticalFit; } set { if (SetStruct(ref m_VerticalFit, value)) SetDirty(); } }
[SerializeField]
protected float m_maxWidth = 0;
public float maxWidth { get { return m_maxWidth; } set { if (SetStruct(ref m_maxWidth, value)) SetDirty(); } }
[SerializeField]
protected float m_maxHeight = 0;
public float maxHeight { get { return m_maxHeight; } set { if (SetStruct(ref m_maxHeight, value)) SetDirty(); } }
[System.NonSerialized]
private RectTransform m_Rect;
private RectTransform rectTransform
{
get
{
if (m_Rect == null)
{
m_Rect = GetComponent<RectTransform>();
}
return m_Rect;
}
}
private DrivenRectTransformTracker m_Tracker;
protected MultiAxisContentSizeFitter()
{ }
#region Unity Lifetime calls
protected override void OnEnable()
{
base.OnEnable();
SetDirty();
}
protected override void OnDisable()
{
m_Tracker.Clear();
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
base.OnDisable();
}
#endregion
protected override void OnRectTransformDimensionsChange()
{
SetDirty();
}
private void HandleSelfFittingAlongAxis(int axis)
{
FitMode fitting = (axis == 0 ? horizontalFit : verticalFit);
if (fitting == FitMode.Unconstrained)
{
// Keep a reference to the tracked transform, but don't control its properties:
m_Tracker.Add(this, rectTransform, DrivenTransformProperties.None);
return;
}
float max = (axis == 0 ? m_maxWidth : m_maxHeight);
max = (max == 0 ? float.MaxValue : max);
m_Tracker.Add(this, rectTransform, (axis == 0 ? DrivenTransformProperties.SizeDeltaX : DrivenTransformProperties.SizeDeltaY));
// Set size to min or preferred size
if (fitting == FitMode.MinSize)
rectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, Mathf.Min(LayoutUtility.GetMinSize(m_Rect, axis), max));
else
rectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, Mathf.Min(LayoutUtility.GetPreferredSize(m_Rect, axis), max));
}
public virtual void SetLayoutHorizontal()
{
m_Tracker.Clear();
HandleSelfFittingAlongAxis(0);
}
public virtual void SetLayoutVertical()
{
HandleSelfFittingAlongAxis(1);
}
protected void SetDirty()
{
if (!IsActive()) return;
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
}
public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
{
if (currentValue.Equals(newValue)) return false;
currentValue = newValue;
return true;
}
#if UNITY_EDITOR
protected override void OnValidate()
{
SetDirty();
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment