#region Copyright RenGuiYou. All rights reserved. //===================================================== // NeatlyFrameWork // Author: RenGuiyou // Feedback: mailto:750539605@qq.com //===================================================== #endregion using System; using UnityEngine; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.UI; namespace Neatly.UI { public abstract class NMaskableGraphic : Graphic, IClippable, IMaskable, IMaterialModifier { [NonSerialized] protected bool m_ShouldRecalculateStencil = true; [NonSerialized] protected Material m_MaskMaterial; [NonSerialized] private RectMask2D m_ParentMask; [NonSerialized] private bool m_Maskable = true; private bool m_IsMaskingGraphic = false; [Serializable] public class CullStateChangedEvent : UnityEvent { } // Event delegates triggered on click. // [SerializeField] // private CullStateChangedEvent m_OnCullStateChanged = new CullStateChangedEvent(); // public CullStateChangedEvent onCullStateChanged // { // get { return m_OnCullStateChanged; } // set { m_OnCullStateChanged = value; } // } public bool maskable { get { return m_Maskable; } set { if (value == m_Maskable) return; m_Maskable = value; m_ShouldRecalculateStencil = true; SetMaterialDirty(); } } public bool isMaskingGraphic { get { return m_IsMaskingGraphic; } set { if (value == m_IsMaskingGraphic) return; m_IsMaskingGraphic = value; } } [NonSerialized] protected int m_StencilValue; [NonSerialized] protected bool m_IsQuestMask; [NonSerialized] protected Mask m_MaskComponent; protected Mask MaskComponent { get { if (m_IsQuestMask) return m_MaskComponent; m_MaskComponent = GetComponent(); m_IsQuestMask = true; return m_MaskComponent; } } public virtual Material GetModifiedMaterial(Material baseMaterial) { var toUse = baseMaterial; if (m_ShouldRecalculateStencil) { var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform); m_StencilValue = maskable ? MaskUtilities.GetStencilDepth(transform, rootCanvas) : 0; m_ShouldRecalculateStencil = false; } // if we have a Mask component then it will // generate the mask material. This is an optimisation // it adds some coupling between components though :( if (m_StencilValue > 0 && MaskComponent == null) { var maskMat = StencilMaterial.Add(toUse, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0); StencilMaterial.Remove(m_MaskMaterial); m_MaskMaterial = maskMat; toUse = m_MaskMaterial; } return toUse; } public virtual void Cull(Rect clipRect, bool validRect) { var cull = !validRect || !clipRect.Overlaps(rootCanvasRect, true); UpdateCull(cull); } private void UpdateCull(bool cull) { if (canvasRenderer.cull != cull) { canvasRenderer.cull = cull; // UISystemProfilerApi.AddMarker("MaskableGraphic.cullingChanged", this); OnCullingChanged(); } } public virtual void SetClipRect(Rect clipRect, bool validRect) { if (validRect) canvasRenderer.EnableRectClipping(clipRect); else canvasRenderer.DisableRectClipping(); } public virtual void SetClipSoftness(Vector2 clipSoftness) { #if UNITY_2019_1_OR_NEWER canvasRenderer.clippingSoftness = clipSoftness; #endif } protected override void OnEnable() { base.OnEnable(); m_ShouldRecalculateStencil = true; UpdateClipParent(); SetMaterialDirty(); if (isMaskingGraphic) { MaskUtilities.NotifyStencilStateChanged(this); } } protected override void OnDisable() { base.OnDisable(); m_ShouldRecalculateStencil = true; SetMaterialDirty(); UpdateClipParent(); StencilMaterial.Remove(m_MaskMaterial); m_MaskMaterial = null; if (isMaskingGraphic) { MaskUtilities.NotifyStencilStateChanged(this); } } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); m_ShouldRecalculateStencil = true; UpdateClipParent(); SetMaterialDirty(); } #endif protected override void OnTransformParentChanged() { base.OnTransformParentChanged(); if (!isActiveAndEnabled) return; m_ShouldRecalculateStencil = true; UpdateClipParent(); SetMaterialDirty(); } protected override void OnCanvasHierarchyChanged() { base.OnCanvasHierarchyChanged(); if (!isActiveAndEnabled) return; m_ShouldRecalculateStencil = true; UpdateClipParent(); SetMaterialDirty(); } readonly Vector3[] m_Corners = new Vector3[4]; private Rect rootCanvasRect { get { rectTransform.GetWorldCorners(m_Corners); if (canvas) { Matrix4x4 mat = canvas.rootCanvas.transform.worldToLocalMatrix; for (int i = 0; i < 4; ++i) m_Corners[i] = mat.MultiplyPoint(m_Corners[i]); } // bounding box is now based on the min and max of all corners (case 1013182) Vector2 min = m_Corners[0]; Vector2 max = m_Corners[0]; for (int i = 1; i < 4; i++) { min.x = Mathf.Min(m_Corners[i].x, min.x); min.y = Mathf.Min(m_Corners[i].y, min.y); max.x = Mathf.Max(m_Corners[i].x, max.x); max.y = Mathf.Max(m_Corners[i].y, max.y); } return new Rect(min, max - min); } } private void UpdateClipParent() { var newParent = (maskable && IsActive()) ? MaskUtilities.GetRectMaskForClippable(this) : null; // if the new parent is different OR is now inactive if (m_ParentMask != null && (newParent != m_ParentMask || !newParent.IsActive())) { m_ParentMask.RemoveClippable(this); UpdateCull(false); } // don't re-add it if the newparent is inactive if (newParent != null && newParent.IsActive()) newParent.AddClippable(this); m_ParentMask = newParent; } public virtual void RecalculateClipping() { UpdateClipParent(); } public virtual void RecalculateMasking() { StencilMaterial.Remove(m_MaskMaterial); m_MaskMaterial = null; m_ShouldRecalculateStencil = true; SetMaterialDirty(); } } }