MaskableGraphic.cs 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. using System;
  2. using UnityEngine.Events;
  3. using UnityEngine.Rendering;
  4. namespace UnityEngine.UI
  5. {
  6. /// <summary>
  7. /// A Graphic that is capable of being masked out.
  8. /// </summary>
  9. public abstract class MaskableGraphic : Graphic, IClippable, IMaskable, IMaterialModifier
  10. {
  11. [NonSerialized]
  12. protected bool m_ShouldRecalculateStencil = true;
  13. [NonSerialized]
  14. protected Material m_MaskMaterial;
  15. [NonSerialized]
  16. private RectMask2D m_ParentMask;
  17. // m_Maskable is whether this graphic is allowed to be masked or not. It has the matching public property maskable.
  18. // The default for m_Maskable is true, so graphics under a mask are masked out of the box.
  19. // The maskable property can be turned off from script by the user if masking is not desired.
  20. // m_IncludeForMasking is whether we actually consider this graphic for masking or not - this is an implementation detail.
  21. // m_IncludeForMasking should only be true if m_Maskable is true AND a parent of the graphic has an IMask component.
  22. // Things would still work correctly if m_IncludeForMasking was always true when m_Maskable is, but performance would suffer.
  23. [SerializeField]
  24. private bool m_Maskable = true;
  25. private bool m_IsMaskingGraphic = false;
  26. [NonSerialized]
  27. [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
  28. [Obsolete("Not used anymore.", true)]
  29. protected bool m_IncludeForMasking = false;
  30. [Serializable]
  31. public class CullStateChangedEvent : UnityEvent<bool> {}
  32. // Event delegates triggered on click.
  33. [SerializeField]
  34. private CullStateChangedEvent m_OnCullStateChanged = new CullStateChangedEvent();
  35. /// <summary>
  36. /// Callback issued when culling changes.
  37. /// </summary>
  38. /// <remarks>
  39. /// Called whene the culling state of this MaskableGraphic either becomes culled or visible. You can use this to control other elements of your UI as culling happens.
  40. /// </remarks>
  41. public CullStateChangedEvent onCullStateChanged
  42. {
  43. get { return m_OnCullStateChanged; }
  44. set { m_OnCullStateChanged = value; }
  45. }
  46. /// <summary>
  47. /// Does this graphic allow masking.
  48. /// </summary>
  49. public bool maskable
  50. {
  51. get { return m_Maskable; }
  52. set
  53. {
  54. if (value == m_Maskable)
  55. return;
  56. m_Maskable = value;
  57. m_ShouldRecalculateStencil = true;
  58. SetMaterialDirty();
  59. }
  60. }
  61. /// <summary>
  62. /// Is this graphic the graphic on the same object as a Mask that is enabled.
  63. /// </summary>
  64. /// <remarks>
  65. /// If toggled ensure to call MaskUtilities.NotifyStencilStateChanged(this); manually as it changes how stenciles are calculated for this image.
  66. /// </remarks>
  67. public bool isMaskingGraphic
  68. {
  69. get { return m_IsMaskingGraphic; }
  70. set
  71. {
  72. if (value == m_IsMaskingGraphic)
  73. return;
  74. m_IsMaskingGraphic = value;
  75. }
  76. }
  77. [NonSerialized]
  78. [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
  79. [Obsolete("Not used anymore", true)]
  80. protected bool m_ShouldRecalculate = true;
  81. [NonSerialized]
  82. protected int m_StencilValue;
  83. /// <summary>
  84. /// See IMaterialModifier.GetModifiedMaterial
  85. /// </summary>
  86. public virtual Material GetModifiedMaterial(Material baseMaterial)
  87. {
  88. var toUse = baseMaterial;
  89. if (m_ShouldRecalculateStencil)
  90. {
  91. if (maskable)
  92. {
  93. var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
  94. m_StencilValue = MaskUtilities.GetStencilDepth(transform, rootCanvas);
  95. }
  96. else
  97. m_StencilValue = 0;
  98. m_ShouldRecalculateStencil = false;
  99. }
  100. // if we have a enabled Mask component then it will
  101. // generate the mask material. This is an optimization
  102. // it adds some coupling between components though :(
  103. if (m_StencilValue > 0 && !isMaskingGraphic)
  104. {
  105. var maskMat = StencilMaterial.Add(toUse, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0);
  106. StencilMaterial.Remove(m_MaskMaterial);
  107. m_MaskMaterial = maskMat;
  108. toUse = m_MaskMaterial;
  109. }
  110. return toUse;
  111. }
  112. /// <summary>
  113. /// See IClippable.Cull
  114. /// </summary>
  115. public virtual void Cull(Rect clipRect, bool validRect)
  116. {
  117. var cull = !validRect || !clipRect.Overlaps(rootCanvasRect, true);
  118. UpdateCull(cull);
  119. }
  120. private void UpdateCull(bool cull)
  121. {
  122. if (canvasRenderer.cull != cull)
  123. {
  124. canvasRenderer.cull = cull;
  125. UISystemProfilerApi.AddMarker("MaskableGraphic.cullingChanged", this);
  126. m_OnCullStateChanged.Invoke(cull);
  127. OnCullingChanged();
  128. }
  129. }
  130. /// <summary>
  131. /// See IClippable.SetClipRect
  132. /// </summary>
  133. public virtual void SetClipRect(Rect clipRect, bool validRect)
  134. {
  135. if (validRect)
  136. canvasRenderer.EnableRectClipping(clipRect);
  137. else
  138. canvasRenderer.DisableRectClipping();
  139. }
  140. public virtual void SetClipSoftness(Vector2 clipSoftness)
  141. {
  142. canvasRenderer.clippingSoftness = clipSoftness;
  143. }
  144. protected override void OnEnable()
  145. {
  146. base.OnEnable();
  147. m_ShouldRecalculateStencil = true;
  148. UpdateClipParent();
  149. SetMaterialDirty();
  150. if (isMaskingGraphic)
  151. {
  152. MaskUtilities.NotifyStencilStateChanged(this);
  153. }
  154. }
  155. protected override void OnDisable()
  156. {
  157. base.OnDisable();
  158. m_ShouldRecalculateStencil = true;
  159. SetMaterialDirty();
  160. UpdateClipParent();
  161. StencilMaterial.Remove(m_MaskMaterial);
  162. m_MaskMaterial = null;
  163. if (isMaskingGraphic)
  164. {
  165. MaskUtilities.NotifyStencilStateChanged(this);
  166. }
  167. }
  168. #if UNITY_EDITOR
  169. protected override void OnValidate()
  170. {
  171. base.OnValidate();
  172. m_ShouldRecalculateStencil = true;
  173. UpdateClipParent();
  174. SetMaterialDirty();
  175. }
  176. #endif
  177. protected override void OnTransformParentChanged()
  178. {
  179. base.OnTransformParentChanged();
  180. if (!isActiveAndEnabled)
  181. return;
  182. m_ShouldRecalculateStencil = true;
  183. UpdateClipParent();
  184. SetMaterialDirty();
  185. }
  186. [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
  187. [Obsolete("Not used anymore.", true)]
  188. public virtual void ParentMaskStateChanged() {}
  189. protected override void OnCanvasHierarchyChanged()
  190. {
  191. base.OnCanvasHierarchyChanged();
  192. if (!isActiveAndEnabled)
  193. return;
  194. m_ShouldRecalculateStencil = true;
  195. UpdateClipParent();
  196. SetMaterialDirty();
  197. }
  198. readonly Vector3[] m_Corners = new Vector3[4];
  199. private Rect rootCanvasRect
  200. {
  201. get
  202. {
  203. rectTransform.GetWorldCorners(m_Corners);
  204. if (canvas)
  205. {
  206. Matrix4x4 mat = canvas.rootCanvas.transform.worldToLocalMatrix;
  207. for (int i = 0; i < 4; ++i)
  208. m_Corners[i] = mat.MultiplyPoint(m_Corners[i]);
  209. }
  210. // bounding box is now based on the min and max of all corners (case 1013182)
  211. Vector2 min = m_Corners[0];
  212. Vector2 max = m_Corners[0];
  213. for (int i = 1; i < 4; i++)
  214. {
  215. min.x = Mathf.Min(m_Corners[i].x, min.x);
  216. min.y = Mathf.Min(m_Corners[i].y, min.y);
  217. max.x = Mathf.Max(m_Corners[i].x, max.x);
  218. max.y = Mathf.Max(m_Corners[i].y, max.y);
  219. }
  220. return new Rect(min, max - min);
  221. }
  222. }
  223. private void UpdateClipParent()
  224. {
  225. var newParent = (maskable && IsActive()) ? MaskUtilities.GetRectMaskForClippable(this) : null;
  226. // if the new parent is different OR is now inactive
  227. if (m_ParentMask != null && (newParent != m_ParentMask || !newParent.IsActive()))
  228. {
  229. m_ParentMask.RemoveClippable(this);
  230. UpdateCull(false);
  231. }
  232. // don't re-add it if the newparent is inactive
  233. if (newParent != null && newParent.IsActive())
  234. newParent.AddClippable(this);
  235. m_ParentMask = newParent;
  236. }
  237. /// <summary>
  238. /// See IClippable.RecalculateClipping
  239. /// </summary>
  240. public virtual void RecalculateClipping()
  241. {
  242. UpdateClipParent();
  243. }
  244. /// <summary>
  245. /// See IMaskable.RecalculateMasking
  246. /// </summary>
  247. public virtual void RecalculateMasking()
  248. {
  249. // Remove the material reference as either the graphic of the mask has been enable/ disabled.
  250. // This will cause the material to be repopulated from the original if need be. (case 994413)
  251. StencilMaterial.Remove(m_MaskMaterial);
  252. m_MaskMaterial = null;
  253. m_ShouldRecalculateStencil = true;
  254. SetMaterialDirty();
  255. }
  256. }
  257. }