You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
351 lines
11 KiB
351 lines
11 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine.EventSystems;
|
|
|
|
namespace UnityEngine.UI
|
|
{
|
|
[AddComponentMenu("UI/Rect Mask 2D", 13)]
|
|
[ExecuteAlways]
|
|
[DisallowMultipleComponent]
|
|
[RequireComponent(typeof(RectTransform))]
|
|
/// <summary>
|
|
/// A 2D rectangular mask that allows for clipping / masking of areas outside the mask.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The RectMask2D behaves in a similar way to a standard Mask component. It differs though in some of the restrictions that it has.
|
|
/// A RectMask2D:
|
|
/// *Only works in the 2D plane
|
|
/// *Requires elements on the mask to be coplanar.
|
|
/// *Does not require stencil buffer / extra draw calls
|
|
/// *Requires fewer draw calls
|
|
/// *Culls elements that are outside the mask area.
|
|
/// </remarks>
|
|
public class RectMask2D : UIBehaviour, IClipper, ICanvasRaycastFilter
|
|
{
|
|
[NonSerialized]
|
|
private readonly RectangularVertexClipper m_VertexClipper = new RectangularVertexClipper();
|
|
|
|
[NonSerialized]
|
|
private RectTransform m_RectTransform;
|
|
|
|
[NonSerialized]
|
|
private HashSet<MaskableGraphic> m_MaskableTargets = new HashSet<MaskableGraphic>();
|
|
|
|
[NonSerialized]
|
|
private HashSet<IClippable> m_ClipTargets = new HashSet<IClippable>();
|
|
|
|
[NonSerialized]
|
|
private bool m_ShouldRecalculateClipRects;
|
|
|
|
[NonSerialized]
|
|
private List<RectMask2D> m_Clippers = new List<RectMask2D>();
|
|
|
|
[NonSerialized]
|
|
private Rect m_LastClipRectCanvasSpace;
|
|
[NonSerialized]
|
|
private bool m_ForceClip;
|
|
|
|
[SerializeField]
|
|
private Vector4 m_Padding = new Vector4();
|
|
|
|
/// <summary>
|
|
/// Padding to be applied to the masking
|
|
/// X = Left
|
|
/// Y = Bottom
|
|
/// Z = Right
|
|
/// W = Top
|
|
/// </summary>
|
|
public Vector4 padding
|
|
{
|
|
get { return m_Padding; }
|
|
set
|
|
{
|
|
m_Padding = value;
|
|
MaskUtilities.Notify2DMaskStateChanged(this);
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
private Vector2Int m_Softness;
|
|
|
|
/// <summary>
|
|
/// The softness to apply to the horizontal and vertical axis.
|
|
/// </summary>
|
|
public Vector2Int softness
|
|
{
|
|
get { return m_Softness; }
|
|
set
|
|
{
|
|
m_Softness.x = Mathf.Max(0, value.x);
|
|
m_Softness.y = Mathf.Max(0, value.y);
|
|
MaskUtilities.Notify2DMaskStateChanged(this);
|
|
}
|
|
}
|
|
|
|
/// <remarks>
|
|
/// Returns a non-destroyed instance or a null reference.
|
|
/// </remarks>
|
|
[NonSerialized] private Canvas m_Canvas;
|
|
private Canvas Canvas
|
|
{
|
|
get
|
|
{
|
|
if (m_Canvas == null)
|
|
{
|
|
var list = ListPool<Canvas>.Get();
|
|
gameObject.GetComponentsInParent(false, list);
|
|
if (list.Count > 0)
|
|
m_Canvas = list[list.Count - 1];
|
|
else
|
|
m_Canvas = null;
|
|
ListPool<Canvas>.Release(list);
|
|
}
|
|
|
|
return m_Canvas;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the Rect for the mask in canvas space.
|
|
/// </summary>
|
|
public Rect canvasRect
|
|
{
|
|
get
|
|
{
|
|
return m_VertexClipper.GetCanvasRect(rectTransform, Canvas);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper function to get the RectTransform for the mask.
|
|
/// </summary>
|
|
public RectTransform rectTransform
|
|
{
|
|
get { return m_RectTransform ?? (m_RectTransform = GetComponent<RectTransform>()); }
|
|
}
|
|
|
|
protected RectMask2D()
|
|
{}
|
|
|
|
protected override void OnEnable()
|
|
{
|
|
base.OnEnable();
|
|
m_ShouldRecalculateClipRects = true;
|
|
ClipperRegistry.Register(this);
|
|
MaskUtilities.Notify2DMaskStateChanged(this);
|
|
}
|
|
|
|
protected override void OnDisable()
|
|
{
|
|
// we call base OnDisable first here
|
|
// as we need to have the IsActive return the
|
|
// correct value when we notify the children
|
|
// that the mask state has changed.
|
|
base.OnDisable();
|
|
m_ClipTargets.Clear();
|
|
m_MaskableTargets.Clear();
|
|
m_Clippers.Clear();
|
|
ClipperRegistry.Unregister(this);
|
|
MaskUtilities.Notify2DMaskStateChanged(this);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
protected override void OnValidate()
|
|
{
|
|
base.OnValidate();
|
|
m_ShouldRecalculateClipRects = true;
|
|
|
|
// Dont allow negative softness.
|
|
m_Softness.x = Mathf.Max(0, m_Softness.x);
|
|
m_Softness.y = Mathf.Max(0, m_Softness.y);
|
|
|
|
if (!IsActive())
|
|
return;
|
|
|
|
MaskUtilities.Notify2DMaskStateChanged(this);
|
|
}
|
|
|
|
#endif
|
|
|
|
public virtual bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
|
|
{
|
|
if (!isActiveAndEnabled)
|
|
return true;
|
|
|
|
return RectTransformUtility.RectangleContainsScreenPoint(rectTransform, sp, eventCamera, m_Padding);
|
|
}
|
|
|
|
private Vector3[] m_Corners = new Vector3[4];
|
|
|
|
private Rect rootCanvasRect
|
|
{
|
|
get
|
|
{
|
|
rectTransform.GetWorldCorners(m_Corners);
|
|
|
|
if (!ReferenceEquals(Canvas, null))
|
|
{
|
|
Canvas rootCanvas = Canvas.rootCanvas;
|
|
for (int i = 0; i < 4; ++i)
|
|
m_Corners[i] = rootCanvas.transform.InverseTransformPoint(m_Corners[i]);
|
|
}
|
|
|
|
return new Rect(m_Corners[0].x, m_Corners[0].y, m_Corners[2].x - m_Corners[0].x, m_Corners[2].y - m_Corners[0].y);
|
|
}
|
|
}
|
|
|
|
public virtual void PerformClipping()
|
|
{
|
|
if (ReferenceEquals(Canvas, null))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//TODO See if an IsActive() test would work well here or whether it might cause unexpected side effects (re case 776771)
|
|
|
|
// if the parents are changed
|
|
// or something similar we
|
|
// do a recalculate here
|
|
if (m_ShouldRecalculateClipRects)
|
|
{
|
|
MaskUtilities.GetRectMasksForClip(this, m_Clippers);
|
|
m_ShouldRecalculateClipRects = false;
|
|
}
|
|
|
|
// get the compound rects from
|
|
// the clippers that are valid
|
|
bool validRect = true;
|
|
Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);
|
|
|
|
// If the mask is in ScreenSpaceOverlay/Camera render mode, its content is only rendered when its rect
|
|
// overlaps that of the root canvas.
|
|
RenderMode renderMode = Canvas.rootCanvas.renderMode;
|
|
bool maskIsCulled =
|
|
(renderMode == RenderMode.ScreenSpaceCamera || renderMode == RenderMode.ScreenSpaceOverlay) &&
|
|
!clipRect.Overlaps(rootCanvasRect, true);
|
|
|
|
if (maskIsCulled)
|
|
{
|
|
// Children are only displayed when inside the mask. If the mask is culled, then the children
|
|
// inside the mask are also culled. In that situation, we pass an invalid rect to allow callees
|
|
// to avoid some processing.
|
|
clipRect = Rect.zero;
|
|
validRect = false;
|
|
}
|
|
|
|
if (clipRect != m_LastClipRectCanvasSpace)
|
|
{
|
|
foreach (IClippable clipTarget in m_ClipTargets)
|
|
{
|
|
clipTarget.SetClipRect(clipRect, validRect);
|
|
}
|
|
|
|
foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
|
|
{
|
|
maskableTarget.SetClipRect(clipRect, validRect);
|
|
maskableTarget.Cull(clipRect, validRect);
|
|
}
|
|
}
|
|
else if (m_ForceClip)
|
|
{
|
|
foreach (IClippable clipTarget in m_ClipTargets)
|
|
{
|
|
clipTarget.SetClipRect(clipRect, validRect);
|
|
}
|
|
|
|
foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
|
|
{
|
|
maskableTarget.SetClipRect(clipRect, validRect);
|
|
|
|
if (maskableTarget.canvasRenderer.hasMoved)
|
|
maskableTarget.Cull(clipRect, validRect);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
|
|
{
|
|
//Case 1170399 - hasMoved is not a valid check when animating on pivot of the object
|
|
maskableTarget.Cull(clipRect, validRect);
|
|
}
|
|
}
|
|
|
|
m_LastClipRectCanvasSpace = clipRect;
|
|
m_ForceClip = false;
|
|
|
|
UpdateClipSoftness();
|
|
}
|
|
|
|
public virtual void UpdateClipSoftness()
|
|
{
|
|
if (ReferenceEquals(Canvas, null))
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (IClippable clipTarget in m_ClipTargets)
|
|
{
|
|
clipTarget.SetClipSoftness(m_Softness);
|
|
}
|
|
|
|
foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
|
|
{
|
|
maskableTarget.SetClipSoftness(m_Softness);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a IClippable to be tracked by the mask.
|
|
/// </summary>
|
|
/// <param name="clippable">Add the clippable object for this mask</param>
|
|
public void AddClippable(IClippable clippable)
|
|
{
|
|
if (clippable == null)
|
|
return;
|
|
m_ShouldRecalculateClipRects = true;
|
|
MaskableGraphic maskable = clippable as MaskableGraphic;
|
|
|
|
if (maskable == null)
|
|
m_ClipTargets.Add(clippable);
|
|
else
|
|
m_MaskableTargets.Add(maskable);
|
|
|
|
m_ForceClip = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove an IClippable from being tracked by the mask.
|
|
/// </summary>
|
|
/// <param name="clippable">Remove the clippable object from this mask</param>
|
|
public void RemoveClippable(IClippable clippable)
|
|
{
|
|
if (clippable == null)
|
|
return;
|
|
|
|
m_ShouldRecalculateClipRects = true;
|
|
clippable.SetClipRect(new Rect(), false);
|
|
|
|
MaskableGraphic maskable = clippable as MaskableGraphic;
|
|
|
|
if (maskable == null)
|
|
m_ClipTargets.Remove(clippable);
|
|
else
|
|
m_MaskableTargets.Remove(maskable);
|
|
|
|
m_ForceClip = true;
|
|
}
|
|
|
|
protected override void OnTransformParentChanged()
|
|
{
|
|
base.OnTransformParentChanged();
|
|
m_ShouldRecalculateClipRects = true;
|
|
}
|
|
|
|
protected override void OnCanvasHierarchyChanged()
|
|
{
|
|
m_Canvas = null;
|
|
base.OnCanvasHierarchyChanged();
|
|
m_ShouldRecalculateClipRects = true;
|
|
}
|
|
}
|
|
}
|
|
|