| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- 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;
- }
- }
- }
|