using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using System.Collections.Generic; namespace NBShaderEditor { public enum MixedBool { False = 0, True = 1, Mixed = -1 } public class ShaderGUIItem { public const float MinLabelWidth = 120f; public const float LabelWidthRatio = 0.4f; public const float GlobalRectXOffset = -15f; public const float GlobalRectWidthExpansion = 15f; public const float UnityEditorGUIIndentWidth = 15f; public const float EditorGUIIndentWidth = 10f; public const float FoldOutArrowWidth = UnityEditorGUIIndentWidth; public const float ControlResetGap = 3f; public const float ControlIndentCompensation = 10f; public ShaderPropertyInfo PropertyInfo; public ShaderGUIItem ParentItem; public List ChildrenItemList = new List(); public ShaderGUIRootItem RootItem; public string PropertyName; public GUIContent GuiContent; private static int _parentControlDisabledDepth; public virtual void InitTriggerByChild()//根Child一定要做这个事情 { if ( PropertyInfo == null && PropertyName != null) { PropertyInfo = RootItem.PropertyInfoDic[PropertyName]; } CheckIsPropertyModified(); } public ShaderGUIItem(ShaderGUIRootItem rtItem,ShaderGUIItem parentItem=null) { RootItem = rtItem; if (parentItem != null) { ParentItem = parentItem; if (!parentItem.ChildrenItemList.Contains(this)) { ParentItem.ChildrenItemList.Add(this); } } } public Rect BaseRect; public Rect LabelRect; public Rect ControlRect; public Rect ResetRect; private static readonly GUIContent TempHelpContent = new GUIContent(); public static float ResetButtonSize => EditorGUIUtility.singleLineHeight; #if !UNITY_2022_1_OR_NEWER private static Material s_ShaderDefaultIntValueMaterial; #endif public static int GetShaderPropertyDefaultIntValue(Shader shader, int propertyIndex) { #if UNITY_2022_1_OR_NEWER return shader.GetPropertyDefaultIntValue(propertyIndex); #else if (s_ShaderDefaultIntValueMaterial == null || s_ShaderDefaultIntValueMaterial.shader != shader) { if (s_ShaderDefaultIntValueMaterial != null) { UnityEngine.Object.DestroyImmediate(s_ShaderDefaultIntValueMaterial); } // Unity 2021.3 has no int default API; use one hidden material as the shader default-value reader. s_ShaderDefaultIntValueMaterial = new Material(shader) { hideFlags = HideFlags.HideAndDontSave }; } return s_ShaderDefaultIntValueMaterial.GetInteger(shader.GetPropertyName(propertyIndex)); #endif } public virtual void GetRect(bool applyControlIndentCompensation = true) { BaseRect = ApplyGlobalRectCompensation(RootItem.GetControlRect()); SplitLineRect(BaseRect, out LabelRect, out ControlRect, out ResetRect, applyControlIndentCompensation); } public static Rect ApplyGlobalRectCompensation(Rect rect) { rect.x += GlobalRectXOffset; rect.width = Mathf.Max(0f, rect.width + GlobalRectWidthExpansion); return rect; } public static Rect ApplyLabelIndentWidth(Rect rect) { float indentDelta = EditorGUI.indentLevel * (EditorGUIIndentWidth - UnityEditorGUIIndentWidth); rect.x += indentDelta; rect.width = Mathf.Max(0f, rect.width - indentDelta); return rect; } public static Rect ApplyDirectLabelIndentWidth(Rect rect) { float indentOffset = EditorGUI.indentLevel * EditorGUIIndentWidth; rect.x += indentOffset; rect.width = Mathf.Max(0f, rect.width - indentOffset); return rect; } public static float GetEditorLabelTextX(Rect labelRect) { return labelRect.x + EditorGUI.indentLevel * UnityEditorGUIIndentWidth; } public static float GetLabelWidth(Rect baseRect) { return Mathf.Max(MinLabelWidth, baseRect.width * LabelWidthRatio); } public static void SplitLineRect( Rect baseRect, out Rect labelRect, out Rect controlRect, out Rect resetRect, bool applyControlIndentCompensation = true) { float labelWidth = GetLabelWidth(baseRect); labelRect = baseRect; labelRect.width = labelWidth; labelRect = ApplyLabelIndentWidth(labelRect); Rect controlAndResetRect = baseRect; controlAndResetRect.x += labelWidth; controlAndResetRect.width = Mathf.Max(0f, controlAndResetRect.width - labelWidth); SplitControlAndResetRect(controlAndResetRect, out controlRect, out resetRect, applyControlIndentCompensation); } protected static GUIContent LocalizedContent( string tableName, string labelKey, string labelFallback, string tooltipKey = null, string tooltipFallback = "") { return ShaderGUILocalization.MakeContent(tableName, labelKey, labelFallback, tooltipKey, tooltipFallback); } protected static GUIContent LocalizedInspectorContent(string tableName, string key, string fallback, string tip = "") { return ShaderGUILocalization.MakeInspectorContent(tableName, key, fallback, tip); } protected static string LocalizedText(string tableName, string key, string fallback = "") { return ShaderGUILocalization.GetInspectorText(tableName, key, fallback); } protected static string[] LocalizedOptions(string tableName, string key, string[] fallback) { return ShaderGUILocalization.GetInspectorOptions(tableName, key, fallback); } public static void SplitControlAndResetRect( Rect baseRect, out Rect controlRect, out Rect resetRect, bool applyControlIndentCompensation = true) { resetRect = baseRect; resetRect.x = baseRect.xMax - ResetButtonSize; resetRect.width = ResetButtonSize; float controlIndentCompensation = applyControlIndentCompensation ? ControlIndentCompensation : 0f; controlRect = baseRect; controlRect.x -= controlIndentCompensation; controlRect.width = Mathf.Max(0f, baseRect.width - ResetButtonSize - ControlResetGap + controlIndentCompensation); } protected Rect LayoutRect(float height = -1f) { return RootItem.GetControlRect(height); } protected void LayoutSpace(float height = -1f) { RootItem.Space(height); } protected void DrawLayoutHelpBox(string message, MessageType messageType) { TempHelpContent.text = message; float width = Mathf.Max(1f, EditorGUIUtility.currentViewWidth + GlobalRectWidthExpansion + GlobalRectXOffset); float height = Mathf.Max( EditorGUIUtility.singleLineHeight * 2f, EditorStyles.helpBox.CalcHeight(TempHelpContent, width)); Rect rect = ApplyGlobalRectCompensation(LayoutRect(height)); EditorGUI.HelpBox(rect, message, messageType); TempHelpContent.text = string.Empty; } public virtual void OnGUI() { GetRect(); using (ParentControlDisabledScope()) { EditorGUI.LabelField(LabelRect, GuiContent); } using (ParentControlDisabledScope()) { EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = PropertyInfo.Property.hasMixedValue; bool animatedPropertyScope = BeginAnimatedPropertyBackground(BaseRect, PropertyInfo.Property); using (new EditorGUIIndentLevelScope(0)) { DrawController(); } EndAnimatedPropertyBackground(animatedPropertyScope); EditorGUI.showMixedValue = false; if (EditorGUI.EndChangeCheck()) { OnEndChange(); } } DrawResetButton(); DrawBlock(); } public void DrawResetButton()//如果重写OnGUI,一定要记得调用DrawResetButton { CheckIsPropertyModified(); _resetButtonContent.text = HasModified ? "R" : ""; _resetButtonStyle = HasModified ? GUI.skin.button : GUI.skin.label; using (ParentControlDisabledScope()) { if (GUI.Button(ResetRect, _resetButtonContent, _resetButtonStyle)) { ExecuteReset(); } } } public virtual void DrawController() { } protected static bool SetFloatIfDifferent(MaterialProperty property, float value) { if (property == null || Mathf.Approximately(property.floatValue, value)) { return false; } property.floatValue = value; return true; } protected static bool SetVectorIfDifferent(MaterialProperty property, Vector4 value) { if (property == null || Approximately(property.vectorValue, value)) { return false; } property.vectorValue = value; return true; } protected static bool SetColorIfDifferent(MaterialProperty property, Color value) { if (property == null) { return false; } Color current = property.colorValue; if (Mathf.Approximately(current.r, value.r) && Mathf.Approximately(current.g, value.g) && Mathf.Approximately(current.b, value.b) && Mathf.Approximately(current.a, value.a)) { return false; } property.colorValue = value; return true; } protected readonly struct EditorGUIIndentLevelScope : System.IDisposable { private readonly int _indentLevel; public EditorGUIIndentLevelScope(int indentLevel) { _indentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = indentLevel; } public void Dispose() { EditorGUI.indentLevel = _indentLevel; } } protected bool IsParentControlDisabled => _parentControlDisabledDepth > 0; protected EditorGUI.DisabledScope ParentControlDisabledScope(bool disabled = false) { return new EditorGUI.DisabledScope(IsParentControlDisabled || disabled); } protected readonly struct InheritedControlDisabledScope : System.IDisposable { private readonly bool _disabled; public InheritedControlDisabledScope(bool disabled) { _disabled = disabled; if (_disabled) { _parentControlDisabledDepth++; } } public void Dispose() { if (_disabled) { _parentControlDisabledDepth = Mathf.Max(0, _parentControlDisabledDepth - 1); } } } public virtual void DrawBlock() { } public virtual void OnEndChange() { CheckIsPropertyModified(); } private readonly Dictionary _animationPropertyPathDic = new Dictionary(); protected bool BeginAnimatedPropertyBackground(Rect totalPosition, MaterialProperty property) { if (property == null || RootItem?.MatEditor == null) { return false; } RootItem.MatEditor.BeginAnimatedCheck(totalPosition, property); return true; } protected void EndAnimatedPropertyBackground(bool scopeActive) { if (scopeActive && RootItem?.MatEditor != null) { RootItem.MatEditor.EndAnimatedCheck(); } } public bool IsPropertyAnimated(string propertyName, params string[] componentNames) { if (propertyName == null) return false; if (AnimationMode.InAnimationMode()) { foreach (var r in RootItem.RenderersUsingThisMaterial) { if (componentNames != null && componentNames.Length > 0) { foreach (string componentName in componentNames) { if (AnimationMode.IsPropertyAnimated(r, GetAnimationPropertyPath(propertyName, componentName))) { return true; } } } else if (AnimationMode.IsPropertyAnimated(r, GetAnimationPropertyPath(propertyName, string.Empty))) { return true; } } } return false; } private string GetAnimationPropertyPath(string propertyName, string componentName) { string key = string.IsNullOrEmpty(componentName) ? propertyName : propertyName + "." + componentName; if (!_animationPropertyPathDic.TryGetValue(key, out string propertyPath)) { propertyPath = string.IsNullOrEmpty(componentName) ? "material." + propertyName : "material." + propertyName + "." + componentName; _animationPropertyPathDic.Add(key, propertyPath); } return propertyPath; } #region ResetLogic private GUIContent _resetButtonContent = new GUIContent("R","重置当前属性及子集属性(如有)"); private GUIStyle _resetButtonStyle; public bool HasModified = false; public bool PropertyIsDefaultValue = true; public virtual void CheckIsPropertyModified(bool isCallByChild = false) { if (!isCallByChild)//只有自身Reset了,才需要查询自己的状态是否正确 { bool isDefaultValue = true; if (PropertyInfo != null) { switch (ShaderGUIUnityCompat.GetPropertyType(PropertyInfo.Property)) { case ShaderPropertyType.Float: case ShaderPropertyType.Range: float defaultValue = RootItem.Shader.GetPropertyDefaultFloatValue(PropertyInfo.Index); isDefaultValue = Mathf.Approximately(PropertyInfo.Property.floatValue, defaultValue); break; case ShaderPropertyType.Color: Vector4 defaultColor = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index); isDefaultValue = Approximately(PropertyInfo.Property.colorValue, defaultColor); break; case ShaderPropertyType.Vector: Vector4 defaultVector = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index); isDefaultValue = Approximately(PropertyInfo.Property.vectorValue, defaultVector); break; } } else { isDefaultValue = true;//如果没有Property,则是看子集的情况 } if (isDefaultValue == PropertyIsDefaultValue) { return;//如果状态没有改变,就不需要做任何操作 } else { PropertyIsDefaultValue = isDefaultValue; } } HasModified = !PropertyIsDefaultValue; foreach (var childItem in ChildrenItemList) { HasModified |= childItem.HasModified; } ParentItem?.CheckIsPropertyModified(true); } public virtual void ExecuteReset(bool isCallByParent = false) { if (PropertyInfo != null) { switch (ShaderGUIUnityCompat.GetPropertyType(PropertyInfo.Property)) { case ShaderPropertyType.Float: case ShaderPropertyType.Range: float defaultValue = RootItem.Shader.GetPropertyDefaultFloatValue(PropertyInfo.Index); PropertyInfo.Property.floatValue = defaultValue; break; case ShaderPropertyType.Color: PropertyInfo.Property.colorValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index); break; case ShaderPropertyType.Vector: PropertyInfo.Property.vectorValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index); break; } PropertyIsDefaultValue = true; } foreach (var childItem in ChildrenItemList) { childItem.ExecuteReset(true); } HasModified = false; if (!isCallByParent)//直接由用户触发重置 { ParentItem?.CheckIsPropertyModified(true); } } protected static bool Approximately(Vector4 a, Vector4 b) { return Mathf.Approximately(a.x, b.x) && Mathf.Approximately(a.y, b.y) && Mathf.Approximately(a.z, b.z) && Mathf.Approximately(a.w, b.w); } #endregion } }