NB_FX
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class BlockItem : ShaderGUIItem
|
||||
{
|
||||
protected readonly ShaderGUIRootItem SharedRootItem;
|
||||
private readonly ShaderGUIFoldOutHelper _foldOutHelper;
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
|
||||
protected virtual GUIStyle TitleStyle => EditorStyles.label;
|
||||
protected virtual bool DrawSeparatorLine => false;
|
||||
|
||||
public string FoldOutPropertyName { get; }
|
||||
|
||||
public BlockItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string foldOutPropertyName,
|
||||
Func<GUIContent> contentProvider) : base(rootItem, parentItem)
|
||||
{
|
||||
SharedRootItem = rootItem;
|
||||
FoldOutPropertyName = foldOutPropertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
GuiContent = _contentProvider();
|
||||
_foldOutHelper = new ShaderGUIFoldOutHelper(rootItem, foldOutPropertyName);
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
DrawLeadingSpace();
|
||||
GetRect();
|
||||
_foldOutHelper.DrawFoldOut(LabelRect);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent, TitleStyle);
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
EditorGUI.indentLevel++;
|
||||
if (_foldOutHelper.BeginFadeGroup())
|
||||
{
|
||||
DrawBlock();
|
||||
}
|
||||
_foldOutHelper.EndFadedGroup();
|
||||
EditorGUI.indentLevel--;
|
||||
if (DrawSeparatorLine)
|
||||
{
|
||||
Rect rect = ApplyGlobalRectCompensation(LayoutRect(1));
|
||||
EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 0.5f));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void DrawLeadingSpace()
|
||||
{
|
||||
}
|
||||
|
||||
public override void DrawBlock()
|
||||
{
|
||||
for (int i = 0; i < ChildrenItemList.Count; i++)
|
||||
{
|
||||
ChildrenItemList[i].OnGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BigBlockItem : BlockItem
|
||||
{
|
||||
protected override GUIStyle TitleStyle => EditorStyles.boldLabel;
|
||||
protected override bool DrawSeparatorLine => true;
|
||||
|
||||
public BigBlockItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string foldOutPropertyName,
|
||||
Func<GUIContent> contentProvider) : base(rootItem, parentItem, foldOutPropertyName, contentProvider)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void DrawLeadingSpace()
|
||||
{
|
||||
LayoutSpace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d07eceb7cee2d8b46ab0f6f62c4f82e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class ColorItem : ShaderGUIItem
|
||||
{
|
||||
private const float ColorFieldLeftInset = EditorGUIIndentWidth;
|
||||
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public ColorItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect(false);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
Color color = PropertyInfo.Property.colorValue;
|
||||
bool hdr = ShaderGUIUnityCompat.HasHdrFlag(PropertyInfo.Property);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.showMixedValue = PropertyInfo.Property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Rect colorRect = GetLabeledColorFieldRect(ControlRect);
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(colorRect, PropertyInfo.Property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
color = EditorGUI.ColorField(colorRect, GUIContent.none, color, true, true, hdr);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
PropertyInfo.Property.colorValue = color;
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
DrawBlock();
|
||||
}
|
||||
|
||||
internal static Rect GetNoLabelColorFieldRect(Rect rect)
|
||||
{
|
||||
rect.x += ColorFieldLeftInset;
|
||||
rect.width = Mathf.Max(0f, rect.width - ColorFieldLeftInset);
|
||||
return rect;
|
||||
}
|
||||
|
||||
internal static Rect GetLabeledColorFieldRect(Rect rect)
|
||||
{
|
||||
float leftPadding = EditorStyles.colorField.padding.left + ColorFieldLeftInset +1f;
|
||||
rect.x -= leftPadding;
|
||||
rect.width += leftPadding;
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
|
||||
public class ColorLineItem : ShaderGUIItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly bool _showLabel;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public ColorLineItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
bool showLabel,
|
||||
Func<GUIContent> contentProvider = null,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_showLabel = showLabel;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _showLabel ? _contentProvider() : GUIContent.none;
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Draw(ApplyGlobalRectCompensation(LayoutRect()));
|
||||
DrawBlock();
|
||||
}
|
||||
|
||||
public void Draw(Rect rect)
|
||||
{
|
||||
BaseRect = rect;
|
||||
|
||||
if (_showLabel)
|
||||
{
|
||||
SplitLineRect(rect, out LabelRect, out ControlRect, out ResetRect, false);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LabelRect = new Rect(rect.x, rect.y, 0f, rect.height);
|
||||
SplitControlAndResetRect(rect, out ControlRect, out ResetRect, false);
|
||||
}
|
||||
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
Color color = property.colorValue;
|
||||
bool hdr = ShaderGUIUnityCompat.HasHdrFlag(property);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Rect colorRect = _showLabel
|
||||
? ColorItem.GetLabeledColorFieldRect(ControlRect)
|
||||
: ColorItem.GetNoLabelColorFieldRect(ControlRect);
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(colorRect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
color = EditorGUI.ColorField(colorRect, GUIContent.none, color, true, true, hdr);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.colorValue = color;
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (!isCallByChild)
|
||||
{
|
||||
bool isDefaultValue = true;
|
||||
if (PropertyInfo != null)
|
||||
{
|
||||
Vector4 defaultColor = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
isDefaultValue = !PropertyInfo.Property.hasMixedValue &&
|
||||
Approximately(PropertyInfo.Property.colorValue, defaultColor);
|
||||
}
|
||||
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (ShaderGUIItem childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31b44f82d6184d46b683b144f9b5f517
|
||||
timeCreated: 1777032000
|
||||
@@ -0,0 +1,533 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class GradientItem : ShaderGUIItem
|
||||
{
|
||||
private static bool s_UndoRedoRegistered;
|
||||
private static int s_UndoRedoVersion;
|
||||
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly string[] _colorPropertyNames;
|
||||
private readonly string[] _alphaPropertyNames;
|
||||
private readonly int _maxCount;
|
||||
private readonly bool _hdr;
|
||||
private readonly ColorSpace _colorSpace;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
private ShaderPropertyInfo[] _colorPropertyInfos;
|
||||
private ShaderPropertyInfo[] _alphaPropertyInfos;
|
||||
private readonly Gradient _gradient = new Gradient();
|
||||
private GradientColorKey[] _colorKeys = Array.Empty<GradientColorKey>();
|
||||
private GradientAlphaKey[] _alphaKeys = Array.Empty<GradientAlphaKey>();
|
||||
private bool _gradientCacheDirty = true;
|
||||
private int _lastUndoRedoVersion = -1;
|
||||
|
||||
public GradientItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string countPropertyName,
|
||||
int maxCount,
|
||||
string[] colorPropertyNames,
|
||||
string[] alphaPropertyNames,
|
||||
Func<GUIContent> contentProvider,
|
||||
bool hdr = false,
|
||||
ColorSpace colorSpace = ColorSpace.Gamma,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = countPropertyName;
|
||||
_maxCount = Mathf.Max(2, maxCount);
|
||||
_colorPropertyNames = colorPropertyNames ?? Array.Empty<string>();
|
||||
_alphaPropertyNames = alphaPropertyNames ?? Array.Empty<string>();
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_hdr = hdr;
|
||||
_colorSpace = colorSpace;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
EnsureUndoRedoRegistered();
|
||||
CacheProperties();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
private static void EnsureUndoRedoRegistered()
|
||||
{
|
||||
if (s_UndoRedoRegistered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Undo.undoRedoPerformed += OnUndoRedoPerformed;
|
||||
s_UndoRedoRegistered = true;
|
||||
}
|
||||
|
||||
private static void OnUndoRedoPerformed()
|
||||
{
|
||||
s_UndoRedoVersion++;
|
||||
}
|
||||
|
||||
private void CacheProperties()
|
||||
{
|
||||
_colorPropertyInfos = new ShaderPropertyInfo[_colorPropertyNames.Length];
|
||||
for (int i = 0; i < _colorPropertyNames.Length; i++)
|
||||
{
|
||||
if (RootItem.PropertyInfoDic.TryGetValue(_colorPropertyNames[i], out ShaderPropertyInfo info))
|
||||
{
|
||||
_colorPropertyInfos[i] = info;
|
||||
}
|
||||
}
|
||||
|
||||
_alphaPropertyInfos = new ShaderPropertyInfo[_alphaPropertyNames.Length];
|
||||
for (int i = 0; i < _alphaPropertyNames.Length; i++)
|
||||
{
|
||||
if (RootItem.PropertyInfoDic.TryGetValue(_alphaPropertyNames[i], out ShaderPropertyInfo info))
|
||||
{
|
||||
_alphaPropertyInfos[i] = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
Gradient gradient = GetCachedGradient();
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = GradientPropertyHasMixedValue();
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, PropertyInfo.Property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
gradient = EditorGUI.GradientField(ControlRect, GUIContent.none, gradient, _hdr, _colorSpace);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
WriteGradient(gradient);
|
||||
MarkGradientCacheDirty();
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
private Gradient GetCachedGradient()
|
||||
{
|
||||
if (_lastUndoRedoVersion != s_UndoRedoVersion)
|
||||
{
|
||||
_lastUndoRedoVersion = s_UndoRedoVersion;
|
||||
MarkGradientCacheDirty();
|
||||
}
|
||||
|
||||
if (_gradientCacheDirty)
|
||||
{
|
||||
ReadGradient();
|
||||
_gradientCacheDirty = false;
|
||||
GradientReflectionHelper.RefreshGradientData();
|
||||
}
|
||||
|
||||
return _gradient;
|
||||
}
|
||||
|
||||
private void MarkGradientCacheDirty()
|
||||
{
|
||||
_gradientCacheDirty = true;
|
||||
}
|
||||
|
||||
private void ReadGradient()
|
||||
{
|
||||
bool hasColorProperties = _colorPropertyInfos.Length > 0;
|
||||
bool hasAlphaProperties = _alphaPropertyInfos.Length > 0;
|
||||
bool isBlackAndWhiteGradient = !hasColorProperties && hasAlphaProperties;
|
||||
GetGradientKeyCounts(out int colorKeyCount, out int alphaKeyCount);
|
||||
|
||||
if (_colorKeys.Length != colorKeyCount)
|
||||
{
|
||||
_colorKeys = new GradientColorKey[colorKeyCount];
|
||||
}
|
||||
|
||||
if (_alphaKeys.Length != alphaKeyCount)
|
||||
{
|
||||
_alphaKeys = new GradientAlphaKey[alphaKeyCount];
|
||||
}
|
||||
|
||||
for (int i = 0; i < colorKeyCount; i++)
|
||||
{
|
||||
Color color = Color.white;
|
||||
float colorTime = colorKeyCount == 1 ? 0f : i / (float)(colorKeyCount - 1);
|
||||
if (isBlackAndWhiteGradient)
|
||||
{
|
||||
TryReadBlackAndWhiteKey(i, ref color, ref colorTime);
|
||||
}
|
||||
else if (i < _colorPropertyInfos.Length && _colorPropertyInfos[i] != null)
|
||||
{
|
||||
Color packed = _colorPropertyInfos[i].Property.colorValue;
|
||||
color = packed;
|
||||
colorTime = packed.a;
|
||||
}
|
||||
|
||||
_colorKeys[i] = new GradientColorKey(color, colorTime);
|
||||
}
|
||||
|
||||
for (int i = 0; i < alphaKeyCount; i++)
|
||||
{
|
||||
float alpha = 1f;
|
||||
float alphaTime = alphaKeyCount == 1 ? 0f : i / (float)(alphaKeyCount - 1);
|
||||
if (!isBlackAndWhiteGradient && hasAlphaProperties)
|
||||
{
|
||||
TryReadAlphaKey(i, ref alpha, ref alphaTime);
|
||||
}
|
||||
|
||||
_alphaKeys[i] = new GradientAlphaKey(alpha, alphaTime);
|
||||
}
|
||||
|
||||
_gradient.SetKeys(_colorKeys, _alphaKeys);
|
||||
}
|
||||
|
||||
private void GetGradientKeyCounts(out int colorKeyCount, out int alphaKeyCount)
|
||||
{
|
||||
int countValue = PropertyInfo.Property.intValue;
|
||||
bool hasColorProperties = _colorPropertyInfos.Length > 0;
|
||||
bool hasAlphaProperties = _alphaPropertyInfos.Length > 0;
|
||||
|
||||
if (hasColorProperties && hasAlphaProperties)
|
||||
{
|
||||
colorKeyCount = countValue & 0xFFFF;
|
||||
alphaKeyCount = countValue >> 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
colorKeyCount = countValue;
|
||||
alphaKeyCount = 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void TryReadBlackAndWhiteKey(int index, ref Color color, ref float time)
|
||||
{
|
||||
int vectorIndex = index / 2;
|
||||
int componentIndex = index % 2;
|
||||
if (vectorIndex >= _alphaPropertyInfos.Length || _alphaPropertyInfos[vectorIndex] == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector4 packed = _alphaPropertyInfos[vectorIndex].Property.vectorValue;
|
||||
float value = componentIndex == 0 ? packed.x : packed.z;
|
||||
time = componentIndex == 0 ? packed.y : packed.w;
|
||||
color = new Color(value, value, value, 1f);
|
||||
}
|
||||
|
||||
private void TryReadAlphaKey(int index, ref float alpha, ref float time)
|
||||
{
|
||||
int vectorIndex = index / 2;
|
||||
int componentIndex = index % 2;
|
||||
if (vectorIndex >= _alphaPropertyInfos.Length || _alphaPropertyInfos[vectorIndex] == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector4 packed = _alphaPropertyInfos[vectorIndex].Property.vectorValue;
|
||||
if (componentIndex == 0)
|
||||
{
|
||||
alpha = packed.x;
|
||||
time = packed.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
alpha = packed.z;
|
||||
time = packed.w;
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteGradient(Gradient gradient)
|
||||
{
|
||||
bool hasColorProperties = _colorPropertyInfos.Length > 0;
|
||||
bool hasAlphaProperties = _alphaPropertyInfos.Length > 0;
|
||||
bool isBlackAndWhiteGradient = !hasColorProperties && hasAlphaProperties;
|
||||
int countPropertyValue = PropertyInfo.Property.intValue;
|
||||
|
||||
if (isBlackAndWhiteGradient)
|
||||
{
|
||||
int finalColorKeyCount = gradient.colorKeys.Length;
|
||||
if (finalColorKeyCount <= _maxCount)
|
||||
{
|
||||
WriteBlackAndWhiteGradient(gradient, finalColorKeyCount);
|
||||
PropertyInfo.Property.intValue = finalColorKeyCount;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasColorProperties)
|
||||
{
|
||||
int finalColorKeyCount = gradient.colorKeys.Length;
|
||||
if (finalColorKeyCount <= _maxCount)
|
||||
{
|
||||
WriteColorKeys(gradient, finalColorKeyCount);
|
||||
countPropertyValue &= 0xFFFF << 16;
|
||||
countPropertyValue |= finalColorKeyCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isBlackAndWhiteGradient && hasAlphaProperties)
|
||||
{
|
||||
int finalAlphaKeyCount = gradient.alphaKeys.Length;
|
||||
if (finalAlphaKeyCount <= _maxCount)
|
||||
{
|
||||
WriteAlphaKeys(gradient, finalAlphaKeyCount);
|
||||
countPropertyValue &= 0xFFFF;
|
||||
countPropertyValue |= finalAlphaKeyCount << 16;
|
||||
}
|
||||
}
|
||||
|
||||
PropertyInfo.Property.intValue = countPropertyValue;
|
||||
}
|
||||
|
||||
private void WriteColorKeys(Gradient gradient, int colorKeyCount)
|
||||
{
|
||||
for (int i = 0; i < colorKeyCount && i < _colorPropertyInfos.Length; i++)
|
||||
{
|
||||
if (_colorPropertyInfos[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GradientColorKey key = gradient.colorKeys[i];
|
||||
_colorPropertyInfos[i].Property.colorValue = new Color(key.color.r, key.color.g, key.color.b, key.time);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteBlackAndWhiteGradient(Gradient gradient, int colorKeyCount)
|
||||
{
|
||||
int vectorCount = Mathf.CeilToInt(colorKeyCount / 2f);
|
||||
for (int i = 0; i < vectorCount && i < _alphaPropertyInfos.Length; i++)
|
||||
{
|
||||
if (_alphaPropertyInfos[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector4 value = Vector4.zero;
|
||||
GradientColorKey key0 = gradient.colorKeys[i * 2];
|
||||
value.x = key0.color.r;
|
||||
value.y = key0.time;
|
||||
if (i * 2 + 1 < colorKeyCount)
|
||||
{
|
||||
GradientColorKey key1 = gradient.colorKeys[i * 2 + 1];
|
||||
value.z = key1.color.r;
|
||||
value.w = key1.time;
|
||||
}
|
||||
|
||||
_alphaPropertyInfos[i].Property.vectorValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteAlphaKeys(Gradient gradient, int alphaKeyCount)
|
||||
{
|
||||
int vectorCount = Mathf.CeilToInt(alphaKeyCount / 2f);
|
||||
for (int i = 0; i < vectorCount && i < _alphaPropertyInfos.Length; i++)
|
||||
{
|
||||
if (_alphaPropertyInfos[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector4 value = Vector4.zero;
|
||||
GradientAlphaKey key0 = gradient.alphaKeys[i * 2];
|
||||
value.x = key0.alpha;
|
||||
value.y = key0.time;
|
||||
if (i * 2 + 1 < alphaKeyCount)
|
||||
{
|
||||
GradientAlphaKey key1 = gradient.alphaKeys[i * 2 + 1];
|
||||
value.z = key1.alpha;
|
||||
value.w = key1.time;
|
||||
}
|
||||
|
||||
_alphaPropertyInfos[i].Property.vectorValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool GradientPropertyHasMixedValue()
|
||||
{
|
||||
if (PropertyInfo == null || PropertyInfo.Property == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GetGradientKeyCounts(out int colorKeyCount, out int alphaKeyCount);
|
||||
bool hasMixedValue = PropertyInfo.Property.hasMixedValue;
|
||||
for (int i = 0; i < colorKeyCount && i < _colorPropertyInfos.Length; i++)
|
||||
{
|
||||
hasMixedValue |= _colorPropertyInfos[i]?.Property.hasMixedValue == true;
|
||||
}
|
||||
|
||||
int alphaPropertyCount = Mathf.CeilToInt(alphaKeyCount / 2f);
|
||||
for (int i = 0; i < alphaPropertyCount && i < _alphaPropertyInfos.Length; i++)
|
||||
{
|
||||
hasMixedValue |= _alphaPropertyInfos[i]?.Property.hasMixedValue == true;
|
||||
}
|
||||
|
||||
return hasMixedValue;
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (!isCallByChild)
|
||||
{
|
||||
bool isDefaultValue = IsCountDefault();
|
||||
for (int i = 0; i < _colorPropertyInfos.Length; i++)
|
||||
{
|
||||
isDefaultValue &= IsDefault(_colorPropertyInfos[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _alphaPropertyInfos.Length; i++)
|
||||
{
|
||||
isDefaultValue &= IsDefault(_alphaPropertyInfos[i]);
|
||||
}
|
||||
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
ResetCountProperty();
|
||||
for (int i = 0; i < _colorPropertyInfos.Length; i++)
|
||||
{
|
||||
ResetProperty(_colorPropertyInfos[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _alphaPropertyInfos.Length; i++)
|
||||
{
|
||||
ResetProperty(_alphaPropertyInfos[i]);
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = true;
|
||||
HasModified = false;
|
||||
MarkGradientCacheDirty();
|
||||
if (!isCallByParent)
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsCountDefault()
|
||||
{
|
||||
if (PropertyInfo == null || PropertyInfo.Property == null || PropertyInfo.Property.hasMixedValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return PropertyInfo.Property.intValue == GetDefaultInt(PropertyInfo);
|
||||
}
|
||||
|
||||
private void ResetCountProperty()
|
||||
{
|
||||
if (PropertyInfo?.Property == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyInfo.Property.intValue = GetDefaultInt(PropertyInfo);
|
||||
}
|
||||
|
||||
private bool IsDefault(ShaderPropertyInfo info)
|
||||
{
|
||||
if (info == null || info.Property == null || info.Property.hasMixedValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (ShaderGUIUnityCompat.GetPropertyType(info.Property))
|
||||
{
|
||||
case ShaderPropertyType.Float:
|
||||
case ShaderPropertyType.Range:
|
||||
return Mathf.Approximately(info.Property.floatValue, RootItem.Shader.GetPropertyDefaultFloatValue(info.Index));
|
||||
case ShaderPropertyType.Int:
|
||||
return info.Property.intValue == GetDefaultInt(info);
|
||||
case ShaderPropertyType.Color:
|
||||
return Approximately(info.Property.colorValue, RootItem.Shader.GetPropertyDefaultVectorValue(info.Index));
|
||||
case ShaderPropertyType.Vector:
|
||||
return Approximately(info.Property.vectorValue, RootItem.Shader.GetPropertyDefaultVectorValue(info.Index));
|
||||
default:
|
||||
return Mathf.Approximately(info.Property.floatValue, GetDefaultScalar(info));
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetProperty(ShaderPropertyInfo info)
|
||||
{
|
||||
if (info == null || info.Property == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ShaderGUIUnityCompat.GetPropertyType(info.Property))
|
||||
{
|
||||
case ShaderPropertyType.Float:
|
||||
case ShaderPropertyType.Range:
|
||||
info.Property.floatValue = RootItem.Shader.GetPropertyDefaultFloatValue(info.Index);
|
||||
break;
|
||||
case ShaderPropertyType.Int:
|
||||
info.Property.intValue = GetDefaultInt(info);
|
||||
break;
|
||||
case ShaderPropertyType.Color:
|
||||
info.Property.colorValue = RootItem.Shader.GetPropertyDefaultVectorValue(info.Index);
|
||||
break;
|
||||
case ShaderPropertyType.Vector:
|
||||
info.Property.vectorValue = RootItem.Shader.GetPropertyDefaultVectorValue(info.Index);
|
||||
break;
|
||||
default:
|
||||
info.Property.floatValue = GetDefaultScalar(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private float GetDefaultScalar(ShaderPropertyInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
return RootItem.Shader.GetPropertyDefaultFloatValue(info.Index);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return 2f;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetDefaultInt(ShaderPropertyInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
return GetShaderPropertyDefaultIntValue(RootItem.Shader, info.Index);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return Mathf.RoundToInt(GetDefaultScalar(info));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17810b41ec32488faa9fbceaaf8b8fd5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class HelpBoxItem : ShaderGUIItem
|
||||
{
|
||||
private readonly MessageType _messageType;
|
||||
private readonly Func<string> _messageProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public HelpBoxItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
Func<string> messageProvider,
|
||||
MessageType messageType = MessageType.Info,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
_messageProvider = messageProvider ?? (() => string.Empty);
|
||||
_messageType = messageType;
|
||||
_isVisible = isVisible;
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
DrawLayoutHelpBox(_messageProvider(), _messageType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c516e0fb3014df8b96bce4e0925cb45
|
||||
timeCreated: 1777032000
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class RenderQueueItem : ShaderGUIFloatItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Action _onQueueChanged;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public RenderQueueItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Action onQueueChanged = null,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_onQueueChanged = onQueueChanged;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnGUI();
|
||||
}
|
||||
|
||||
public override void OnEndChange()
|
||||
{
|
||||
base.OnEndChange();
|
||||
_onQueueChanged?.Invoke();
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
base.ExecuteReset(isCallByParent);
|
||||
_onQueueChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5caca15eb8794602ae6029d798cb5f1f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class SectionLabelItem : ShaderGUIItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public SectionLabelItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LayoutSpace();
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(ApplyGlobalRectCompensation(LayoutRect()), GuiContent, EditorStyles.boldLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75c41d424c5e4203992222069d060bc6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,394 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class ShaderGUIFloatItem:ShaderGUIItem
|
||||
{
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public ShaderGUIFloatItem(ShaderGUIRootItem rootItem, ShaderGUIItem parentItem, Func<bool> isVisible = null) :
|
||||
base(rootItem, parentItem: parentItem)
|
||||
{
|
||||
_isVisible = isVisible;
|
||||
base.InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnGUI();
|
||||
}
|
||||
|
||||
public override void DrawController()
|
||||
{
|
||||
float value = DraggableLabelFloat.Handle(LabelRect, PropertyInfo.Property.floatValue, sensitivity: -1f);//拖动Label控件可以操作Float参数
|
||||
value = EditorGUI.FloatField(ControlRect, value);
|
||||
SetFloatIfDifferent(PropertyInfo.Property, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ShaderGUISliderItem:ShaderGUIItem
|
||||
{
|
||||
public float Min = 0;
|
||||
public float Max = 1;
|
||||
public string RangePropertyName;
|
||||
ShaderPropertyInfo _rangePropertyInfo;
|
||||
private readonly Func<bool> _isVisible;
|
||||
private const float SliderFrontGap = 5f;
|
||||
private const float SliderBackGap = 2f;
|
||||
private const float RangeFieldWidth = 30f;
|
||||
|
||||
public override void InitTriggerByChild()
|
||||
{
|
||||
if (RangePropertyName != null)
|
||||
{
|
||||
_rangePropertyInfo = RootItem.PropertyInfoDic[RangePropertyName];
|
||||
Min = _rangePropertyInfo.Property.vectorValue.x;
|
||||
Max = _rangePropertyInfo.Property.vectorValue.y;
|
||||
}
|
||||
base.InitTriggerByChild();
|
||||
}
|
||||
|
||||
public ShaderGUISliderItem(ShaderGUIRootItem rootItem, ShaderGUIItem parentItem, Func<bool> isVisible = null) :
|
||||
base(rootItem, parentItem: parentItem)
|
||||
{
|
||||
_isVisible = isVisible;
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_rangePropertyInfo == null)
|
||||
{
|
||||
base.OnGUI();
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
DrawController();
|
||||
}
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
DrawResetButton();
|
||||
DrawBlock();
|
||||
}
|
||||
|
||||
public override void DrawController()
|
||||
{
|
||||
if (_rangePropertyInfo != null)
|
||||
{
|
||||
Rect minRect = ControlRect;
|
||||
minRect.width = RangeFieldWidth;
|
||||
Rect maxRect = ControlRect;
|
||||
maxRect.x = ControlRect.xMax - RangeFieldWidth;
|
||||
maxRect.width = RangeFieldWidth;
|
||||
Rect sliderRect = ControlRect;
|
||||
sliderRect.x = minRect.xMax + SliderFrontGap;
|
||||
sliderRect.width = Mathf.Max(0f, maxRect.x - sliderRect.x - SliderBackGap + EditorGUI.indentLevel * UnityEditorGUIIndentWidth);
|
||||
|
||||
RangeVecHasMixedValue(out bool minValueHasMixed,out bool maxValueHasMixed);
|
||||
|
||||
Vector4 rangeVector = _rangePropertyInfo.Property.vectorValue;
|
||||
float min = rangeVector.x;
|
||||
float max = rangeVector.y;
|
||||
|
||||
EditorGUI.showMixedValue = minValueHasMixed;
|
||||
bool minAnimatedScope = BeginAnimatedPropertyBackground(minRect, _rangePropertyInfo.Property);
|
||||
min = EditorGUI.FloatField(minRect, min);
|
||||
EndAnimatedPropertyBackground(minAnimatedScope);
|
||||
EditorGUI.showMixedValue = maxValueHasMixed;
|
||||
bool maxAnimatedScope = BeginAnimatedPropertyBackground(maxRect, _rangePropertyInfo.Property);
|
||||
max = EditorGUI.FloatField(maxRect, max);
|
||||
EndAnimatedPropertyBackground(maxAnimatedScope);
|
||||
rangeVector.x = min;
|
||||
rangeVector.y = max;
|
||||
SetVectorIfDifferent(_rangePropertyInfo.Property, rangeVector);
|
||||
|
||||
float sliderMin = Mathf.Min(min, max);
|
||||
float sliderMax = Mathf.Max(min, max);
|
||||
float value = DraggableLabelFloat.Handle(
|
||||
LabelRect,
|
||||
PropertyInfo.Property.floatValue,
|
||||
DraggableLabelFloat.GetSensitivityByRange(sliderMin, sliderMax),
|
||||
sliderMin,
|
||||
sliderMax);
|
||||
EditorGUI.showMixedValue = PropertyInfo.Property.hasMixedValue;
|
||||
bool sliderAnimatedScope = BeginAnimatedPropertyBackground(sliderRect, PropertyInfo.Property);
|
||||
value = SliderNoIndent(sliderRect, value, sliderMin, sliderMax);
|
||||
EndAnimatedPropertyBackground(sliderAnimatedScope);
|
||||
|
||||
SetFloatIfDifferent(PropertyInfo.Property, Mathf.Clamp(value, sliderMin, sliderMax));
|
||||
EditorGUI.showMixedValue = false;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
float value = DraggableLabelFloat.Handle(
|
||||
LabelRect,
|
||||
PropertyInfo.Property.floatValue,
|
||||
DraggableLabelFloat.GetSensitivityByRange(Min, Max),
|
||||
Min,
|
||||
Max);
|
||||
value = EditorGUI.Slider(ControlRect, value,Min,Max);
|
||||
SetFloatIfDifferent(PropertyInfo.Property, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static float SliderNoIndent(Rect rect, float value, float min, float max)
|
||||
{
|
||||
int indentLevel = EditorGUI.indentLevel;
|
||||
try
|
||||
{
|
||||
EditorGUI.indentLevel = 0;
|
||||
return EditorGUI.Slider(rect, value, min, max);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorGUI.indentLevel = indentLevel;
|
||||
}
|
||||
}
|
||||
|
||||
void RangeVecHasMixedValue( out bool minValueHasMixed, out bool maxValueHasMixed)
|
||||
{
|
||||
minValueHasMixed = false;
|
||||
maxValueHasMixed = false;
|
||||
if (RootItem.Mats.Count > 1)
|
||||
{
|
||||
MaterialProperty rangeProperty = _rangePropertyInfo.Property;
|
||||
float minValue = 0;
|
||||
float maxValue = 0;
|
||||
for (int i = 0; i < RootItem.Mats.Count; i++)
|
||||
{
|
||||
Vector4 rangeVec = RootItem.Mats[i].GetVector(RangePropertyName);
|
||||
if (i == 0)
|
||||
{
|
||||
minValue = rangeVec.x;
|
||||
maxValue = rangeVec.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Mathf.Approximately(minValue, rangeVec.x))
|
||||
{
|
||||
minValueHasMixed = true;
|
||||
}
|
||||
|
||||
if (!Mathf.Approximately(maxValue, rangeVec.y))
|
||||
{
|
||||
maxValueHasMixed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RangePropIsDefaultValue()
|
||||
{
|
||||
MaterialProperty rangeProperty = _rangePropertyInfo.Property;
|
||||
return rangeProperty.vectorValue == RootItem.Shader.GetPropertyDefaultVectorValue(_rangePropertyInfo.Index) && !rangeProperty.hasMixedValue;
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (RangePropertyName != null)
|
||||
{
|
||||
if (!isCallByChild) //只有自身Reset了,才需要查询自己的状态是否正确
|
||||
{
|
||||
bool isDefaultValue = true;
|
||||
|
||||
float defaultValue = RootItem.Shader.GetPropertyDefaultFloatValue(PropertyInfo.Index);
|
||||
isDefaultValue = Mathf.Approximately(PropertyInfo.Property.floatValue, defaultValue);
|
||||
Vector4 defaultRangeVec = RootItem.Shader.GetPropertyDefaultVectorValue(_rangePropertyInfo.Index);
|
||||
isDefaultValue &= _rangePropertyInfo.Property.vectorValue == defaultRangeVec;
|
||||
base.CheckIsPropertyModified(isCallByChild);
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return; //如果状态没有改变,就不需要做任何操作
|
||||
}
|
||||
else
|
||||
{
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (var childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.CheckIsPropertyModified(isCallByChild);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
if (RangePropertyName != null)
|
||||
{
|
||||
PropertyInfo.Property.floatValue = RootItem.Shader.GetPropertyDefaultFloatValue(PropertyInfo.Index);
|
||||
_rangePropertyInfo.Property.vectorValue =
|
||||
RootItem.Shader.GetPropertyDefaultVectorValue(_rangePropertyInfo.Index);
|
||||
PropertyIsDefaultValue = true;
|
||||
|
||||
foreach (var childItem in ChildrenItemList)
|
||||
{
|
||||
childItem.ExecuteReset(true);
|
||||
}
|
||||
|
||||
HasModified = false;
|
||||
if (!isCallByParent) //直接由用户触发重置
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ExecuteReset(isCallByParent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//用于滑动控制Label的方案。
|
||||
// DraggableLabelFloat.cs
|
||||
public static class DraggableLabelFloat
|
||||
{
|
||||
// 为每个控件实例缓存一次拖拽状态
|
||||
private static readonly System.Collections.Generic.Dictionary<int, DragState> s_Drag =
|
||||
new System.Collections.Generic.Dictionary<int, DragState>();
|
||||
private class DragState
|
||||
{
|
||||
public float startValue;
|
||||
public float dragX; // 累计水平位移(单位:像素)
|
||||
}
|
||||
|
||||
public static float Handle(Rect labelRect, float value, float sensitivity = -1f, float? min = null, float? max = null)
|
||||
{
|
||||
if (!GUI.enabled)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
EditorGUIUtility.AddCursorRect(labelRect, MouseCursor.SlideArrow);
|
||||
|
||||
int id = GUIUtility.GetControlID(FocusType.Passive, labelRect);
|
||||
Event e = Event.current;
|
||||
|
||||
switch (e.GetTypeForControl(id))
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if (labelRect.Contains(e.mousePosition) && e.button == 0)
|
||||
{
|
||||
GUIUtility.hotControl = id;
|
||||
e.Use();
|
||||
EditorGUIUtility.SetWantsMouseJumping(1);
|
||||
|
||||
s_Drag[id] = new DragState
|
||||
{
|
||||
startValue = value,
|
||||
dragX = 0f
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (GUIUtility.hotControl == id && s_Drag.TryGetValue(id, out var st))
|
||||
{
|
||||
// 关键:使用 Event.delta 累加位移,兼容越界/跳回
|
||||
st.dragX += e.delta.x;
|
||||
|
||||
float baseSens = sensitivity > 0f
|
||||
? sensitivity
|
||||
: 0.003f * Mathf.Max(1f, Mathf.Abs(st.startValue));
|
||||
|
||||
float modifier = 1f;
|
||||
if (e.shift) modifier *= 0.1f;
|
||||
if (e.control || e.command) modifier *= 10f;
|
||||
|
||||
float accel = 1f + 0.15f * Mathf.Clamp01(Mathf.Abs(st.dragX) / 50f);
|
||||
|
||||
float newValue = st.startValue + st.dragX * baseSens * modifier * accel;
|
||||
|
||||
if (min.HasValue) newValue = Mathf.Max(min.Value, newValue);
|
||||
if (max.HasValue) newValue = Mathf.Min(max.Value, newValue);
|
||||
|
||||
if (!float.IsNaN(newValue) && !float.IsInfinity(newValue) && !Mathf.Approximately(newValue, value))
|
||||
{
|
||||
GUI.changed = true;
|
||||
value = newValue;
|
||||
}
|
||||
|
||||
e.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == id)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
EditorGUIUtility.SetWantsMouseJumping(0);
|
||||
s_Drag.Remove(id);
|
||||
e.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.KeyDown:
|
||||
// 可选:ESC 取消拖拽并还原
|
||||
if (GUIUtility.hotControl == id && e.keyCode == KeyCode.Escape && s_Drag.TryGetValue(id, out var st2))
|
||||
{
|
||||
value = st2.startValue;
|
||||
GUI.changed = true;
|
||||
|
||||
GUIUtility.hotControl = 0;
|
||||
EditorGUIUtility.SetWantsMouseJumping(0);
|
||||
s_Drag.Remove(id);
|
||||
e.Use();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static float GetSensitivityByRange(float min, float max)
|
||||
{
|
||||
float range = Mathf.Abs(max - min);
|
||||
if (float.IsNaN(range) || float.IsInfinity(range) || Mathf.Approximately(range, 0f))
|
||||
{
|
||||
return -1f;
|
||||
}
|
||||
|
||||
return range * 0.003f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b82b6bb5065f449f98cdba68daa4c16f
|
||||
timeCreated: 1758462240
|
||||
@@ -0,0 +1,127 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.AnimatedValues;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
|
||||
public class ShaderGUIFoldOutHelper
|
||||
{
|
||||
private const float FoldOutAnimationSpeed = 10f;
|
||||
|
||||
public ShaderGUIFoldOutHelper(ShaderGUIRootItem rootItem, string foldOutPropertyName)
|
||||
{
|
||||
_rootItem = rootItem;
|
||||
_propertyName = foldOutPropertyName;
|
||||
_propertyInfo = _rootItem.PropertyInfoDic[_propertyName];
|
||||
|
||||
bool isOpen = _propertyInfo.Property.floatValue > 0.5f;
|
||||
animBool.value = isOpen;
|
||||
animBool.target = isOpen;
|
||||
animBool.valueChanged.AddListener(rootItem.MatEditor.Repaint);
|
||||
animBool.speed = FoldOutAnimationSpeed;
|
||||
}
|
||||
|
||||
private ShaderGUIRootItem _rootItem;
|
||||
private string _propertyName;
|
||||
private AnimBool animBool = new AnimBool();
|
||||
|
||||
private ShaderPropertyInfo _propertyInfo;
|
||||
|
||||
public bool BeginFadedGroup(Rect labelRect)
|
||||
{
|
||||
DrawFoldOut(labelRect);
|
||||
return BeginFadeGroup();
|
||||
}
|
||||
|
||||
public bool BeginFadeGroup()
|
||||
{
|
||||
return animBool.faded > 0.0001f;
|
||||
}
|
||||
|
||||
public bool DrawFoldOut(Rect labelRect)
|
||||
{
|
||||
Rect foldOutRect = MakeFoldOutRect(labelRect);
|
||||
bool isOpen = _propertyInfo.Property.floatValue > 0.5f;
|
||||
isOpen = GUI.Toggle(foldOutRect, isOpen, GUIContent.none, EditorStyles.foldout);
|
||||
SetOpen(isOpen);
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
public bool DrawFoldOutLabel(Rect labelRect, GUIContent content, GUIStyle style)
|
||||
{
|
||||
Rect foldOutRect = MakeFoldOutRect(labelRect);
|
||||
bool isOpen = _propertyInfo.Property.floatValue > 0.5f;
|
||||
isOpen = GUI.Toggle(foldOutRect, isOpen, content, style ?? EditorStyles.foldout);
|
||||
SetOpen(isOpen);
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
public void SetOpen(bool isOpen)
|
||||
{
|
||||
if (animBool.target != isOpen)
|
||||
{
|
||||
animBool.target = isOpen;
|
||||
}
|
||||
|
||||
float value = isOpen ? 1f : 0f;
|
||||
if (!Mathf.Approximately(_propertyInfo.Property.floatValue, value))
|
||||
{
|
||||
_propertyInfo.Property.floatValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static Rect MakeFoldOutRect(Rect labelRect)
|
||||
{
|
||||
Rect foldOutRect = labelRect;
|
||||
foldOutRect.x = ShaderGUIItem.GetEditorLabelTextX(labelRect) - ShaderGUIItem.FoldOutArrowWidth;
|
||||
foldOutRect.width = Mathf.Max(0f, labelRect.xMax - foldOutRect.x);
|
||||
return foldOutRect;
|
||||
}
|
||||
|
||||
public void EndFadedGroup()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class ShaderGUIBigBlockItem:ShaderGUIItem
|
||||
{
|
||||
ShaderGUIFoldOutHelper _foldOutHelper;
|
||||
GUIStyle _boldStyle = new GUIStyle(EditorStyles.boldLabel);
|
||||
public string FoldOutPropertyName { get; set; }
|
||||
|
||||
public override void InitTriggerByChild()
|
||||
{
|
||||
_foldOutHelper = new ShaderGUIFoldOutHelper(RootItem, FoldOutPropertyName);
|
||||
base.InitTriggerByChild();
|
||||
}
|
||||
|
||||
public ShaderGUIBigBlockItem(ShaderGUIRootItem rootItem, ShaderGUIItem parentItem) :
|
||||
base(rootItem, parentItem: parentItem)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void OnGUI()//完全覆写
|
||||
{
|
||||
LayoutSpace();
|
||||
GetRect();
|
||||
DrawResetButton();
|
||||
bool isOpen = _foldOutHelper.BeginFadedGroup(LabelRect);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent,_boldStyle);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
if (isOpen)
|
||||
{
|
||||
DrawBlock();
|
||||
}
|
||||
_foldOutHelper.EndFadedGroup();
|
||||
EditorGUI.indentLevel--;
|
||||
Rect rect = ApplyGlobalRectCompensation(LayoutRect(1));
|
||||
EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6228baa804b041658eaebea96655b431
|
||||
timeCreated: 1758373869
|
||||
@@ -0,0 +1,517 @@
|
||||
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<ShaderGUIItem> ChildrenItemList = new List<ShaderGUIItem>();
|
||||
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<string, string> _animationPropertyPathDic = new Dictionary<string, string>();
|
||||
|
||||
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
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f948e090bae4a558a52cb56d0b6c9fc
|
||||
timeCreated: 1758127003
|
||||
@@ -0,0 +1,894 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public static class ShaderGUILocalization
|
||||
{
|
||||
public const string DefaultLanguage = "zh-CN";
|
||||
public const string EnglishLanguage = "en-US";
|
||||
|
||||
private const string TooltipColumnSuffix = "-tip";
|
||||
private const string TooltipColumnAliasSuffix = "-tooltip";
|
||||
|
||||
private static readonly Dictionary<string, LocalizationTable> Tables =
|
||||
new Dictionary<string, LocalizationTable>(StringComparer.OrdinalIgnoreCase);
|
||||
private static NBFXLanguageMode s_CachedLanguageMode;
|
||||
private static string s_CachedPreferredLanguage;
|
||||
private static bool s_CachedPreferredLanguageValid;
|
||||
|
||||
public static void RegisterCsv(string tableName, string csvAssetPath)
|
||||
{
|
||||
tableName = NormalizeTableName(tableName);
|
||||
if (Tables.TryGetValue(tableName, out LocalizationTable table))
|
||||
{
|
||||
if (string.Equals(table.CsvAssetPath, csvAssetPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
table.CsvAssetPath = csvAssetPath;
|
||||
table.Reload();
|
||||
return;
|
||||
}
|
||||
|
||||
Tables[tableName] = new LocalizationTable(csvAssetPath);
|
||||
}
|
||||
|
||||
public static string GetCurrentLanguage(string tableName)
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
return GetCurrentLanguage(table);
|
||||
}
|
||||
|
||||
public static string GetEditorLanguageName()
|
||||
{
|
||||
return GetLanguageFromEditor();
|
||||
}
|
||||
|
||||
private static string GetCurrentLanguage(LocalizationTable table)
|
||||
{
|
||||
string preferred = GetPreferredLanguageName();
|
||||
foreach (string language in table.Languages)
|
||||
{
|
||||
if (string.Equals(language, preferred, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return language;
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultLanguage;
|
||||
}
|
||||
|
||||
public static GUIContent MakeContent(
|
||||
string tableName,
|
||||
string labelKey,
|
||||
string labelFallback,
|
||||
string tooltipKey = null,
|
||||
string tooltipFallback = "")
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
string language = GetCurrentLanguage(table);
|
||||
var cacheKey = new ContentCacheKey(language, labelKey, labelFallback, tooltipKey, tooltipFallback);
|
||||
if (table.ContentCache.TryGetValue(cacheKey, out GUIContent cachedContent))
|
||||
{
|
||||
return cachedContent;
|
||||
}
|
||||
|
||||
var content = new GUIContent(
|
||||
GetLocalizedValue(table, labelKey, labelFallback, language),
|
||||
GetTooltip(table, labelKey, tooltipKey, tooltipFallback, language));
|
||||
table.ContentCache[cacheKey] = content;
|
||||
return content;
|
||||
}
|
||||
|
||||
public static GUIContent MakeInspectorContent(string tableName, string key, string fallback, string tip = "")
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
string language = GetCurrentLanguage(table);
|
||||
var cacheKey = new InspectorContentCacheKey(language, key, fallback, tip);
|
||||
if (table.InspectorContentCache.TryGetValue(cacheKey, out GUIContent cachedContent))
|
||||
{
|
||||
return cachedContent;
|
||||
}
|
||||
|
||||
string labelKey = "inspector." + key + ".label";
|
||||
string tooltipKey = "inspector." + key + ".tip";
|
||||
var content = new GUIContent(
|
||||
GetLocalizedValue(table, labelKey, fallback, language),
|
||||
GetTooltip(table, labelKey, tooltipKey, tip, language));
|
||||
table.InspectorContentCache[cacheKey] = content;
|
||||
return content;
|
||||
}
|
||||
|
||||
public static string GetInspectorText(string tableName, string key, string fallback = "")
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
string language = GetCurrentLanguage(table);
|
||||
return GetInspectorText(table, key, fallback, language);
|
||||
}
|
||||
|
||||
public static string GetInspectorText(string tableName, string key, string fallback, string language)
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
return GetInspectorText(table, key, fallback, language);
|
||||
}
|
||||
|
||||
private static string GetInspectorText(LocalizationTable table, string key, string fallback, string language)
|
||||
{
|
||||
var cacheKey = new InspectorTextCacheKey(language, key, fallback);
|
||||
if (table.InspectorTextCache.TryGetValue(cacheKey, out string cachedText))
|
||||
{
|
||||
return cachedText;
|
||||
}
|
||||
|
||||
cachedText = GetLocalizedValue(table, "inspector." + key, fallback, language);
|
||||
table.InspectorTextCache[cacheKey] = cachedText;
|
||||
return cachedText;
|
||||
}
|
||||
|
||||
public static string[] GetInspectorOptions(string tableName, string key, string[] fallback)
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
string language = GetCurrentLanguage(table);
|
||||
return GetInspectorOptions(table, key, fallback, language);
|
||||
}
|
||||
|
||||
public static string[] GetInspectorOptions(string tableName, string key, string[] fallback, string language)
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
return GetInspectorOptions(table, key, fallback, language);
|
||||
}
|
||||
|
||||
private static string[] GetInspectorOptions(LocalizationTable table, string key, string[] fallback, string language)
|
||||
{
|
||||
if (fallback == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var cacheKey = new OptionsCacheKey(language, key, fallback);
|
||||
if (table.InspectorOptionsCache.TryGetValue(cacheKey, out string[] cachedValues))
|
||||
{
|
||||
return cachedValues;
|
||||
}
|
||||
|
||||
string keyPrefix = "inspector." + key + ".option";
|
||||
string[] values = new string[fallback.Length];
|
||||
for (int i = 0; i < fallback.Length; i++)
|
||||
{
|
||||
values[i] = GetLocalizedValue(table, keyPrefix + "." + i, fallback[i], language);
|
||||
}
|
||||
|
||||
table.InspectorOptionsCache[cacheKey] = values;
|
||||
return values;
|
||||
}
|
||||
|
||||
public static string[] GetOptions(string tableName, string keyPrefix, string[] fallback)
|
||||
{
|
||||
if (fallback == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
string language = GetCurrentLanguage(table);
|
||||
var cacheKey = new OptionsCacheKey(language, keyPrefix, fallback);
|
||||
if (table.OptionsCache.TryGetValue(cacheKey, out string[] cachedValues))
|
||||
{
|
||||
return cachedValues;
|
||||
}
|
||||
|
||||
string[] values = new string[fallback.Length];
|
||||
for (int i = 0; i < fallback.Length; i++)
|
||||
{
|
||||
values[i] = GetLocalizedValue(table, keyPrefix + "." + i, fallback[i], language);
|
||||
}
|
||||
|
||||
table.OptionsCache[cacheKey] = values;
|
||||
return values;
|
||||
}
|
||||
|
||||
public static string Get(string tableName, string key, string fallback = "")
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
return GetLocalizedValue(table, key, fallback, GetCurrentLanguage(table));
|
||||
}
|
||||
|
||||
private static string GetLocalizedValue(LocalizationTable table, string key, string fallback, string language)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (table.Rows.TryGetValue(key, out Dictionary<string, string> row))
|
||||
{
|
||||
if (row.TryGetValue(language, out string localizedValue) && !string.IsNullOrEmpty(localizedValue))
|
||||
{
|
||||
return localizedValue;
|
||||
}
|
||||
|
||||
if (row.TryGetValue(DefaultLanguage, out string defaultValue) && !string.IsNullOrEmpty(defaultValue))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public static string GetTooltip(string tableName, string labelKey, string tooltipKey = null, string fallback = "")
|
||||
{
|
||||
LocalizationTable table = GetTable(tableName);
|
||||
table.EnsureLoaded();
|
||||
return GetTooltip(table, labelKey, tooltipKey, fallback, GetCurrentLanguage(table));
|
||||
}
|
||||
|
||||
private static string GetTooltip(
|
||||
LocalizationTable table,
|
||||
string labelKey,
|
||||
string tooltipKey,
|
||||
string fallback,
|
||||
string language)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(labelKey) &&
|
||||
table.Rows.TryGetValue(labelKey, out Dictionary<string, string> row))
|
||||
{
|
||||
if (TryGetTooltip(row, language, out string localizedTooltip))
|
||||
{
|
||||
return localizedTooltip;
|
||||
}
|
||||
|
||||
if (TryGetTooltip(row, DefaultLanguage, out string defaultTooltip))
|
||||
{
|
||||
return defaultTooltip;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(tooltipKey))
|
||||
{
|
||||
return GetLocalizedValue(table, tooltipKey, fallback, language);
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public static void Reload(string tableName = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tableName))
|
||||
{
|
||||
foreach (LocalizationTable table in Tables.Values)
|
||||
{
|
||||
table.Reload();
|
||||
}
|
||||
|
||||
InvalidatePreferredLanguageCache();
|
||||
return;
|
||||
}
|
||||
|
||||
GetTable(tableName).Reload();
|
||||
InvalidatePreferredLanguageCache();
|
||||
}
|
||||
|
||||
private static void InvalidatePreferredLanguageCache()
|
||||
{
|
||||
s_CachedPreferredLanguageValid = false;
|
||||
}
|
||||
|
||||
private static LocalizationTable GetTable(string tableName)
|
||||
{
|
||||
tableName = NormalizeTableName(tableName);
|
||||
if (!Tables.TryGetValue(tableName, out LocalizationTable table))
|
||||
{
|
||||
table = new LocalizationTable(string.Empty);
|
||||
Tables[tableName] = table;
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private static string NormalizeTableName(string tableName)
|
||||
{
|
||||
return string.IsNullOrEmpty(tableName) ? "default" : tableName;
|
||||
}
|
||||
|
||||
private static bool IsTooltipColumn(string header)
|
||||
{
|
||||
return !string.IsNullOrEmpty(header) &&
|
||||
(header.EndsWith(TooltipColumnSuffix, StringComparison.OrdinalIgnoreCase) ||
|
||||
header.EndsWith(TooltipColumnAliasSuffix, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static bool TryGetTooltip(Dictionary<string, string> row, string language, out string tooltip)
|
||||
{
|
||||
tooltip = string.Empty;
|
||||
if (string.IsNullOrEmpty(language))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (row.TryGetValue(language + TooltipColumnSuffix, out tooltip) && !string.IsNullOrEmpty(tooltip))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (row.TryGetValue(language + TooltipColumnAliasSuffix, out tooltip) && !string.IsNullOrEmpty(tooltip))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string GetPreferredLanguageName()
|
||||
{
|
||||
NBFXLanguageMode languageMode = NBFXProjectSettings.LanguageMode;
|
||||
if (s_CachedPreferredLanguageValid && s_CachedLanguageMode == languageMode)
|
||||
{
|
||||
return s_CachedPreferredLanguage;
|
||||
}
|
||||
|
||||
string language;
|
||||
switch (languageMode)
|
||||
{
|
||||
case NBFXLanguageMode.Chinese:
|
||||
language = DefaultLanguage;
|
||||
break;
|
||||
case NBFXLanguageMode.English:
|
||||
language = EnglishLanguage;
|
||||
break;
|
||||
case NBFXLanguageMode.FollowEditor:
|
||||
default:
|
||||
language = GetLanguageFromEditor();
|
||||
break;
|
||||
}
|
||||
|
||||
s_CachedLanguageMode = languageMode;
|
||||
s_CachedPreferredLanguage = language;
|
||||
s_CachedPreferredLanguageValid = true;
|
||||
return language;
|
||||
}
|
||||
|
||||
private static string GetLanguageFromEditor()
|
||||
{
|
||||
if (TryGetEditorLanguageName(out string language))
|
||||
{
|
||||
return language;
|
||||
}
|
||||
|
||||
return GetSystemLanguageName();
|
||||
}
|
||||
|
||||
private static bool TryGetEditorLanguageName(out string language)
|
||||
{
|
||||
language = string.Empty;
|
||||
|
||||
Type localizationDatabaseType = typeof(Editor).Assembly.GetType("UnityEditor.LocalizationDatabase");
|
||||
if (localizationDatabaseType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
|
||||
string[] memberNames =
|
||||
{
|
||||
"currentEditorLanguage",
|
||||
"CurrentEditorLanguage"
|
||||
};
|
||||
|
||||
foreach (string memberName in memberNames)
|
||||
{
|
||||
PropertyInfo propertyInfo = localizationDatabaseType.GetProperty(memberName, flags);
|
||||
if (propertyInfo != null && TryGetEditorLanguageValue(() => propertyInfo.GetValue(null, null), out language))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
FieldInfo fieldInfo = localizationDatabaseType.GetField(memberName, flags);
|
||||
if (fieldInfo != null && TryGetEditorLanguageValue(() => fieldInfo.GetValue(null), out language))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetEditorLanguageValue(Func<object> valueGetter, out string language)
|
||||
{
|
||||
language = string.Empty;
|
||||
try
|
||||
{
|
||||
return TryMapLanguageValue(valueGetter(), out language);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryMapLanguageValue(object value, out string language)
|
||||
{
|
||||
language = string.Empty;
|
||||
if (value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value is SystemLanguage systemLanguage)
|
||||
{
|
||||
language = MapSystemLanguage(systemLanguage);
|
||||
return true;
|
||||
}
|
||||
|
||||
string languageName = value.ToString();
|
||||
if (string.IsNullOrEmpty(languageName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string normalizedLanguageName = languageName.Replace("_", "-").ToLowerInvariant();
|
||||
if (normalizedLanguageName.StartsWith("en", StringComparison.Ordinal) ||
|
||||
normalizedLanguageName.Contains("english"))
|
||||
{
|
||||
language = EnglishLanguage;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (normalizedLanguageName.StartsWith("zh", StringComparison.Ordinal) ||
|
||||
normalizedLanguageName.Contains("chinese"))
|
||||
{
|
||||
language = DefaultLanguage;
|
||||
return true;
|
||||
}
|
||||
|
||||
language = DefaultLanguage;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string GetSystemLanguageName()
|
||||
{
|
||||
return MapSystemLanguage(Application.systemLanguage);
|
||||
}
|
||||
|
||||
private static string MapSystemLanguage(SystemLanguage systemLanguage)
|
||||
{
|
||||
switch (systemLanguage)
|
||||
{
|
||||
case SystemLanguage.English:
|
||||
return EnglishLanguage;
|
||||
case SystemLanguage.Chinese:
|
||||
case SystemLanguage.ChineseSimplified:
|
||||
case SystemLanguage.ChineseTraditional:
|
||||
return DefaultLanguage;
|
||||
default:
|
||||
return DefaultLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct ContentCacheKey : IEquatable<ContentCacheKey>
|
||||
{
|
||||
private readonly string _language;
|
||||
private readonly string _labelKey;
|
||||
private readonly string _labelFallback;
|
||||
private readonly string _tooltipKey;
|
||||
private readonly string _tooltipFallback;
|
||||
|
||||
public ContentCacheKey(
|
||||
string language,
|
||||
string labelKey,
|
||||
string labelFallback,
|
||||
string tooltipKey,
|
||||
string tooltipFallback)
|
||||
{
|
||||
_language = language;
|
||||
_labelKey = labelKey;
|
||||
_labelFallback = labelFallback;
|
||||
_tooltipKey = tooltipKey;
|
||||
_tooltipFallback = tooltipFallback;
|
||||
}
|
||||
|
||||
public bool Equals(ContentCacheKey other)
|
||||
{
|
||||
return StringEquals(_language, other._language) &&
|
||||
StringEquals(_labelKey, other._labelKey) &&
|
||||
StringEquals(_labelFallback, other._labelFallback) &&
|
||||
StringEquals(_tooltipKey, other._tooltipKey) &&
|
||||
StringEquals(_tooltipFallback, other._tooltipFallback);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ContentCacheKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 31 + StringHash(_language);
|
||||
hash = hash * 31 + StringHash(_labelKey);
|
||||
hash = hash * 31 + StringHash(_labelFallback);
|
||||
hash = hash * 31 + StringHash(_tooltipKey);
|
||||
hash = hash * 31 + StringHash(_tooltipFallback);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct InspectorContentCacheKey : IEquatable<InspectorContentCacheKey>
|
||||
{
|
||||
private readonly string _language;
|
||||
private readonly string _key;
|
||||
private readonly string _fallback;
|
||||
private readonly string _tip;
|
||||
|
||||
public InspectorContentCacheKey(string language, string key, string fallback, string tip)
|
||||
{
|
||||
_language = language;
|
||||
_key = key;
|
||||
_fallback = fallback;
|
||||
_tip = tip;
|
||||
}
|
||||
|
||||
public bool Equals(InspectorContentCacheKey other)
|
||||
{
|
||||
return StringEquals(_language, other._language) &&
|
||||
StringEquals(_key, other._key) &&
|
||||
StringEquals(_fallback, other._fallback) &&
|
||||
StringEquals(_tip, other._tip);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is InspectorContentCacheKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 31 + StringHash(_language);
|
||||
hash = hash * 31 + StringHash(_key);
|
||||
hash = hash * 31 + StringHash(_fallback);
|
||||
hash = hash * 31 + StringHash(_tip);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct OptionsCacheKey : IEquatable<OptionsCacheKey>
|
||||
{
|
||||
private readonly string _language;
|
||||
private readonly string _keyPrefix;
|
||||
private readonly string[] _fallback;
|
||||
private readonly int _fallbackLength;
|
||||
|
||||
public OptionsCacheKey(string language, string keyPrefix, string[] fallback)
|
||||
{
|
||||
_language = language;
|
||||
_keyPrefix = keyPrefix;
|
||||
_fallback = fallback;
|
||||
_fallbackLength = fallback != null ? fallback.Length : 0;
|
||||
}
|
||||
|
||||
public bool Equals(OptionsCacheKey other)
|
||||
{
|
||||
if (!StringEquals(_language, other._language) ||
|
||||
!StringEquals(_keyPrefix, other._keyPrefix) ||
|
||||
_fallbackLength != other._fallbackLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _fallbackLength; i++)
|
||||
{
|
||||
if (!StringEquals(_fallback[i], other._fallback[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is OptionsCacheKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 31 + StringHash(_language);
|
||||
hash = hash * 31 + StringHash(_keyPrefix);
|
||||
hash = hash * 31 + _fallbackLength;
|
||||
for (int i = 0; i < _fallbackLength; i++)
|
||||
{
|
||||
hash = hash * 31 + StringHash(_fallback[i]);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct InspectorTextCacheKey : IEquatable<InspectorTextCacheKey>
|
||||
{
|
||||
private readonly string _language;
|
||||
private readonly string _key;
|
||||
private readonly string _fallback;
|
||||
|
||||
public InspectorTextCacheKey(string language, string key, string fallback)
|
||||
{
|
||||
_language = language;
|
||||
_key = key;
|
||||
_fallback = fallback;
|
||||
}
|
||||
|
||||
public bool Equals(InspectorTextCacheKey other)
|
||||
{
|
||||
return StringEquals(_language, other._language) &&
|
||||
StringEquals(_key, other._key) &&
|
||||
StringEquals(_fallback, other._fallback);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is InspectorTextCacheKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 31 + StringHash(_language);
|
||||
hash = hash * 31 + StringHash(_key);
|
||||
hash = hash * 31 + StringHash(_fallback);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool StringEquals(string a, string b)
|
||||
{
|
||||
return string.Equals(a, b, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static int StringHash(string value)
|
||||
{
|
||||
return value == null ? 0 : StringComparer.Ordinal.GetHashCode(value);
|
||||
}
|
||||
|
||||
private sealed class LocalizationTable
|
||||
{
|
||||
private bool _loaded;
|
||||
private bool _warningLogged;
|
||||
|
||||
public LocalizationTable(string csvAssetPath)
|
||||
{
|
||||
CsvAssetPath = csvAssetPath;
|
||||
Rows = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
|
||||
Languages = Array.Empty<string>();
|
||||
ContentCache = new Dictionary<ContentCacheKey, GUIContent>();
|
||||
InspectorContentCache = new Dictionary<InspectorContentCacheKey, GUIContent>();
|
||||
InspectorTextCache = new Dictionary<InspectorTextCacheKey, string>();
|
||||
OptionsCache = new Dictionary<OptionsCacheKey, string[]>();
|
||||
InspectorOptionsCache = new Dictionary<OptionsCacheKey, string[]>();
|
||||
}
|
||||
|
||||
public string CsvAssetPath { get; set; }
|
||||
public Dictionary<string, Dictionary<string, string>> Rows { get; private set; }
|
||||
public string[] Languages { get; private set; }
|
||||
public Dictionary<ContentCacheKey, GUIContent> ContentCache { get; }
|
||||
public Dictionary<InspectorContentCacheKey, GUIContent> InspectorContentCache { get; }
|
||||
public Dictionary<InspectorTextCacheKey, string> InspectorTextCache { get; }
|
||||
public Dictionary<OptionsCacheKey, string[]> OptionsCache { get; }
|
||||
public Dictionary<OptionsCacheKey, string[]> InspectorOptionsCache { get; }
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
_loaded = false;
|
||||
_warningLogged = false;
|
||||
Rows = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
|
||||
Languages = Array.Empty<string>();
|
||||
ContentCache.Clear();
|
||||
InspectorContentCache.Clear();
|
||||
InspectorTextCache.Clear();
|
||||
OptionsCache.Clear();
|
||||
InspectorOptionsCache.Clear();
|
||||
}
|
||||
|
||||
public void EnsureLoaded()
|
||||
{
|
||||
if (_loaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_loaded = true;
|
||||
Rows = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
|
||||
Languages = Array.Empty<string>();
|
||||
|
||||
if (string.IsNullOrEmpty(CsvAssetPath))
|
||||
{
|
||||
LogWarningOnce("ShaderGUI localization table is not registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
TextAsset csvAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(CsvAssetPath);
|
||||
if (csvAsset == null)
|
||||
{
|
||||
LogWarningOnce($"ShaderGUI localization csv not found at '{CsvAssetPath}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
ParseCsv(csvAsset.text);
|
||||
}
|
||||
|
||||
private void ParseCsv(string csvText)
|
||||
{
|
||||
List<string> rows = SplitRows(csvText);
|
||||
if (rows.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<string> headers = ParseCsvRow(rows[0]);
|
||||
if (headers.Count < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var languages = new List<string>();
|
||||
for (int i = 1; i < headers.Count; i++)
|
||||
{
|
||||
if (!IsTooltipColumn(headers[i]))
|
||||
{
|
||||
languages.Add(headers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Languages = languages.ToArray();
|
||||
for (int rowIndex = 1; rowIndex < rows.Count; rowIndex++)
|
||||
{
|
||||
List<string> values = ParseCsvRow(rows[rowIndex]);
|
||||
if (values.Count == 0 || string.IsNullOrWhiteSpace(values[0]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var languageMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
for (int i = 1; i < headers.Count; i++)
|
||||
{
|
||||
languageMap[headers[i]] = i < values.Count ? values[i] : string.Empty;
|
||||
}
|
||||
|
||||
Rows[values[0]] = languageMap;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string> SplitRows(string csvText)
|
||||
{
|
||||
var rows = new List<string>();
|
||||
if (string.IsNullOrEmpty(csvText))
|
||||
{
|
||||
return rows;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
bool inQuotes = false;
|
||||
for (int i = 0; i < csvText.Length; i++)
|
||||
{
|
||||
char current = csvText[i];
|
||||
if (current == '"')
|
||||
{
|
||||
if (inQuotes && i + 1 < csvText.Length && csvText[i + 1] == '"')
|
||||
{
|
||||
builder.Append('"');
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inQuotes && (current == '\n' || current == '\r'))
|
||||
{
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
rows.Add(builder.ToString());
|
||||
builder.Length = 0;
|
||||
}
|
||||
|
||||
if (current == '\r' && i + 1 < csvText.Length && csvText[i + 1] == '\n')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append(current);
|
||||
}
|
||||
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
rows.Add(builder.ToString());
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static List<string> ParseCsvRow(string rowText)
|
||||
{
|
||||
var values = new List<string>();
|
||||
var builder = new StringBuilder();
|
||||
bool inQuotes = false;
|
||||
for (int i = 0; i < rowText.Length; i++)
|
||||
{
|
||||
char current = rowText[i];
|
||||
if (current == '"')
|
||||
{
|
||||
if (inQuotes && i + 1 < rowText.Length && rowText[i + 1] == '"')
|
||||
{
|
||||
builder.Append('"');
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inQuotes && current == ',')
|
||||
{
|
||||
values.Add(builder.ToString());
|
||||
builder.Length = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append(current);
|
||||
}
|
||||
|
||||
values.Add(builder.ToString());
|
||||
return values;
|
||||
}
|
||||
|
||||
private void LogWarningOnce(string message)
|
||||
{
|
||||
if (_warningLogged)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_warningLogged = true;
|
||||
Debug.LogWarning(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5429c73ee5e4590bbbbccee3d5a3a58
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class ShaderGUIPopUpItem:ShaderGUIItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<string[]> _popupNamesProvider;
|
||||
private readonly Action<MaterialProperty> _onValueChanged;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public ShaderGUIPopUpItem(ShaderGUIRootItem rootItem, ShaderGUIItem parentItem) :
|
||||
base(rootItem, parentItem: parentItem)
|
||||
{
|
||||
// base.InitTriggerByChild();
|
||||
}
|
||||
|
||||
public ShaderGUIPopUpItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<string[]> popupNamesProvider,
|
||||
Action<MaterialProperty> onValueChanged = null,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_popupNamesProvider = popupNamesProvider;
|
||||
_onValueChanged = onValueChanged;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
PopUpNames = _popupNamesProvider?.Invoke();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public string[] PopUpNames;
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnGUI();
|
||||
}
|
||||
|
||||
public override void DrawController()
|
||||
{
|
||||
int value = EditorGUI.Popup(ControlRect, (int)PropertyInfo.Property.floatValue, PopUpNames);
|
||||
SetFloatIfDifferent(PropertyInfo.Property, value);
|
||||
}
|
||||
|
||||
public override void OnEndChange()
|
||||
{
|
||||
base.OnEndChange();
|
||||
_onValueChanged?.Invoke(PropertyInfo.Property);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
base.ExecuteReset(isCallByParent);
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
public class ShaderGUIBitMaskItem : ShaderGUIItem
|
||||
{
|
||||
private static readonly string[] EmptyMaskNames = new string[0];
|
||||
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<string[]> _maskNamesProvider;
|
||||
private readonly Action<MaterialProperty> _onValueChanged;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public ShaderGUIBitMaskItem(ShaderGUIRootItem rootItem, ShaderGUIItem parentItem)
|
||||
: base(rootItem, parentItem: parentItem)
|
||||
{
|
||||
}
|
||||
|
||||
public ShaderGUIBitMaskItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<string[]> maskNamesProvider,
|
||||
Action<MaterialProperty> onValueChanged = null,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_maskNamesProvider = maskNamesProvider;
|
||||
_onValueChanged = onValueChanged;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
MaskNames = _maskNamesProvider?.Invoke();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public string[] MaskNames;
|
||||
public int ValidMask = -1;
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_contentProvider != null)
|
||||
{
|
||||
GuiContent = _contentProvider();
|
||||
}
|
||||
|
||||
if (_maskNamesProvider != null)
|
||||
{
|
||||
MaskNames = _maskNamesProvider();
|
||||
}
|
||||
|
||||
base.OnGUI();
|
||||
}
|
||||
|
||||
public override void DrawController()
|
||||
{
|
||||
string[] maskNames = MaskNames ?? EmptyMaskNames;
|
||||
int validMask = GetValidMask(maskNames);
|
||||
int mask = Mathf.RoundToInt(PropertyInfo.Property.floatValue) & validMask;
|
||||
int value = EditorGUI.MaskField(ControlRect, mask, maskNames) & validMask;
|
||||
SetFloatIfDifferent(PropertyInfo.Property, value);
|
||||
}
|
||||
|
||||
public override void OnEndChange()
|
||||
{
|
||||
base.OnEndChange();
|
||||
_onValueChanged?.Invoke(PropertyInfo.Property);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
base.ExecuteReset(isCallByParent);
|
||||
OnEndChange();
|
||||
}
|
||||
|
||||
private int GetValidMask(string[] maskNames)
|
||||
{
|
||||
if (ValidMask >= 0)
|
||||
{
|
||||
return ValidMask;
|
||||
}
|
||||
|
||||
int optionCount = Mathf.Clamp(maskNames.Length, 0, 30);
|
||||
return optionCount == 0 ? 0 : (1 << optionCount) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4147e85b43d4bcaab43dedf111c41fb
|
||||
timeCreated: 1758444036
|
||||
@@ -0,0 +1,288 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using NBShader;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class ShaderPropertyInfo
|
||||
{
|
||||
public MaterialProperty Property;
|
||||
public string Name;
|
||||
public int Index;
|
||||
}
|
||||
public class ShaderGUIRootItem
|
||||
{
|
||||
private static int s_RendererCacheVersion;
|
||||
private static double s_RendererCacheFirstDirtyTime = -1d;
|
||||
private static double s_RendererCacheDirtyTime;
|
||||
private const double RendererCacheHierarchyRefreshDelay = 0.35d;
|
||||
private const double RendererCacheMaxHierarchyRefreshDelay = 2d;
|
||||
private const float DefaultManualLayoutHeight = 1200f;
|
||||
|
||||
public Shader Shader;
|
||||
public MaterialEditor MatEditor;
|
||||
public List<Material> Mats;
|
||||
public Dictionary<string,ShaderPropertyInfo> PropertyInfoDic = new Dictionary<string,ShaderPropertyInfo>();
|
||||
public List<ShaderFlagsBase> ShaderFlags;//各个继承类各自初始化
|
||||
public bool IsInit = true;
|
||||
public Color DefaultBackgroundColor;
|
||||
public bool ClearUnusedTextureReferencesRequested { get; private set; }
|
||||
public List<Renderer> RenderersUsingThisMaterial = new List<Renderer>();
|
||||
public List<ParticleSystemRenderer> ParticleRenderersUsingThisMaterial = new List<ParticleSystemRenderer>();
|
||||
public bool IsUsedByParticleSystem { get; private set; }
|
||||
private int _rendererCacheVersion = -1;
|
||||
private Material _rendererCacheMaterial;
|
||||
private readonly List<Material> _rendererMaterialBuffer = new List<Material>();
|
||||
private bool _manualLayoutActive;
|
||||
private Rect _manualLayoutRect;
|
||||
private float _manualLayoutY;
|
||||
private float _manualLayoutUsedHeight;
|
||||
private float _manualLayoutHeight = DefaultManualLayoutHeight;
|
||||
|
||||
static ShaderGUIRootItem()
|
||||
{
|
||||
EditorApplication.hierarchyChanged += OnHierarchyChanged;
|
||||
}
|
||||
|
||||
private static void OnHierarchyChanged()
|
||||
{
|
||||
double now = EditorApplication.timeSinceStartup;
|
||||
s_RendererCacheVersion++;
|
||||
if (s_RendererCacheFirstDirtyTime < 0d)
|
||||
{
|
||||
s_RendererCacheFirstDirtyTime = now;
|
||||
}
|
||||
|
||||
s_RendererCacheDirtyTime = now;
|
||||
}
|
||||
|
||||
public virtual void OnGUI(MaterialEditor editor,MaterialProperty[] props)
|
||||
{
|
||||
MatEditor = editor;
|
||||
DefaultBackgroundColor = GUI.backgroundColor;
|
||||
if (IsInit || ShouldRebuildMaterialTargets(editor.targets))
|
||||
{
|
||||
IsInit = true;
|
||||
Mats = new List<Material>();
|
||||
foreach (var obj in editor.targets)
|
||||
{
|
||||
Mats.Add(obj as Material);
|
||||
}
|
||||
InitFlags(Mats);
|
||||
Shader = Mats[0].shader;
|
||||
RefreshRendererCacheIfNeeded(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshRendererCacheIfNeeded(false);
|
||||
}
|
||||
if (ShouldRebuildPropertyInfo(props))
|
||||
{
|
||||
PropertyInfoDic.Clear();
|
||||
for (int i = 0; i < props.Length; i++)
|
||||
{
|
||||
ShaderPropertyInfo propInfo = new ShaderPropertyInfo();
|
||||
propInfo.Property = props[i];
|
||||
propInfo.Name = props[i].name;
|
||||
propInfo.Index = i;
|
||||
PropertyInfoDic.Add(propInfo.Name, propInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < props.Length; i++)
|
||||
{
|
||||
ShaderPropertyInfo propInfo = PropertyInfoDic[props[i].name];
|
||||
propInfo.Property = props[i];
|
||||
propInfo.Index = i;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
BeginManualLayout();
|
||||
OnChildOnGUI();
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndManualLayout();
|
||||
ClearUnusedTextureReferencesRequested = false;
|
||||
}
|
||||
|
||||
RepaintWhenAnimationModeActive();
|
||||
IsInit = false;
|
||||
}
|
||||
|
||||
public void RequestClearUnusedTextureReferences()
|
||||
{
|
||||
ClearUnusedTextureReferencesRequested = true;
|
||||
}
|
||||
|
||||
public Rect GetControlRect(float height = -1f)
|
||||
{
|
||||
float rectHeight = height >= 0f ? height : EditorGUIUtility.singleLineHeight;
|
||||
if (!_manualLayoutActive)
|
||||
{
|
||||
return EditorGUILayout.GetControlRect(false, rectHeight);
|
||||
}
|
||||
|
||||
Rect rect = new Rect(_manualLayoutRect.x, _manualLayoutY, _manualLayoutRect.width, rectHeight);
|
||||
_manualLayoutY += rectHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
_manualLayoutUsedHeight = Mathf.Max(
|
||||
_manualLayoutUsedHeight,
|
||||
_manualLayoutY - _manualLayoutRect.y - EditorGUIUtility.standardVerticalSpacing);
|
||||
return rect;
|
||||
}
|
||||
|
||||
public void Space(float height = -1f)
|
||||
{
|
||||
float spaceHeight = height >= 0f ? height : EditorGUIUtility.standardVerticalSpacing;
|
||||
if (!_manualLayoutActive)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
return;
|
||||
}
|
||||
|
||||
_manualLayoutY += spaceHeight;
|
||||
_manualLayoutUsedHeight = Mathf.Max(_manualLayoutUsedHeight, _manualLayoutY - _manualLayoutRect.y);
|
||||
}
|
||||
|
||||
private void BeginManualLayout()
|
||||
{
|
||||
float height = Mathf.Max(EditorGUIUtility.singleLineHeight, _manualLayoutHeight);
|
||||
_manualLayoutRect = EditorGUILayout.GetControlRect(false, height);
|
||||
_manualLayoutY = _manualLayoutRect.y;
|
||||
_manualLayoutUsedHeight = 0f;
|
||||
_manualLayoutActive = true;
|
||||
}
|
||||
|
||||
private void EndManualLayout()
|
||||
{
|
||||
if (!_manualLayoutActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_manualLayoutActive = false;
|
||||
float usedHeight = Mathf.Max(EditorGUIUtility.singleLineHeight, _manualLayoutUsedHeight);
|
||||
if (!Mathf.Approximately(_manualLayoutHeight, usedHeight))
|
||||
{
|
||||
_manualLayoutHeight = usedHeight;
|
||||
MatEditor?.Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldRebuildPropertyInfo(MaterialProperty[] props)
|
||||
{
|
||||
if (PropertyInfoDic.Count != props.Length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < props.Length; i++)
|
||||
{
|
||||
if (!PropertyInfoDic.ContainsKey(props[i].name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void RepaintWhenAnimationModeActive()
|
||||
{
|
||||
if (AnimationMode.InAnimationMode())
|
||||
{
|
||||
MatEditor.Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldRebuildMaterialTargets(UnityEngine.Object[] targets)
|
||||
{
|
||||
if (Mats == null || targets == null || Mats.Count != targets.Length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < targets.Length; i++)
|
||||
{
|
||||
if (Mats[i] != (targets[i] as Material))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RefreshRendererCacheIfNeeded(bool force)
|
||||
{
|
||||
Material material = Mats != null && Mats.Count > 0 ? Mats[0] : null;
|
||||
if (!force && _rendererCacheMaterial == material && _rendererCacheVersion == s_RendererCacheVersion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double now = EditorApplication.timeSinceStartup;
|
||||
bool waitingForQuietHierarchy = now - s_RendererCacheDirtyTime < RendererCacheHierarchyRefreshDelay;
|
||||
bool withinMaxDelay = s_RendererCacheFirstDirtyTime >= 0d &&
|
||||
now - s_RendererCacheFirstDirtyTime < RendererCacheMaxHierarchyRefreshDelay;
|
||||
if (!force &&
|
||||
_rendererCacheMaterial == material &&
|
||||
waitingForQuietHierarchy &&
|
||||
withinMaxDelay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CacheRenderersUsingThisMaterial(material);
|
||||
_rendererCacheMaterial = material;
|
||||
_rendererCacheVersion = s_RendererCacheVersion;
|
||||
s_RendererCacheFirstDirtyTime = -1d;
|
||||
}
|
||||
|
||||
void CacheRenderersUsingThisMaterial(Material material)
|
||||
{
|
||||
Renderer[] renderers = UnityObjectFindCompat.FindAll<Renderer>();
|
||||
RenderersUsingThisMaterial.Clear();
|
||||
ParticleRenderersUsingThisMaterial.Clear();
|
||||
IsUsedByParticleSystem = false;
|
||||
if (material == null || renderers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Renderer renderer in renderers)
|
||||
{
|
||||
if (renderer is ParticleSystemRenderer psr &&
|
||||
(psr.sharedMaterial == material || psr.trailMaterial == material))
|
||||
{
|
||||
IsUsedByParticleSystem = true;
|
||||
ParticleRenderersUsingThisMaterial.Add(psr);
|
||||
}
|
||||
|
||||
_rendererMaterialBuffer.Clear();
|
||||
renderer.GetSharedMaterials(_rendererMaterialBuffer);
|
||||
for (int i = 0; i < _rendererMaterialBuffer.Count; i++)
|
||||
{
|
||||
if (_rendererMaterialBuffer[i] == material)
|
||||
{
|
||||
RenderersUsingThisMaterial.Add(renderer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_rendererMaterialBuffer.Clear();
|
||||
}
|
||||
|
||||
public virtual void InitFlags(List<Material> mats) { } //各个子类各自实现
|
||||
|
||||
public virtual void OnChildOnGUI()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e57b381204754ae780ab305e5feff475
|
||||
timeCreated: 1758206205
|
||||
@@ -0,0 +1,498 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class TextureItem : ShaderGUIItem
|
||||
{
|
||||
private readonly string _texturePropertyName;
|
||||
private readonly TexturePropertyGroupItem _groupItem;
|
||||
private readonly Func<bool> _isVisible;
|
||||
private readonly Action<MaterialProperty> _afterDraw;
|
||||
|
||||
public TextureItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string texturePropertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
string colorPropertyName = null,
|
||||
bool drawScaleOffset = true,
|
||||
Action<MaterialProperty> afterDraw = null,
|
||||
Func<bool> isVisible = null,
|
||||
Func<GUIContent> tillingContentProvider = null,
|
||||
Func<GUIContent> offsetContentProvider = null) : base(rootItem, parentItem)
|
||||
{
|
||||
_texturePropertyName = texturePropertyName;
|
||||
_isVisible = isVisible;
|
||||
_afterDraw = afterDraw;
|
||||
_groupItem = new TexturePropertyGroupItem(
|
||||
rootItem,
|
||||
this,
|
||||
texturePropertyName,
|
||||
colorPropertyName,
|
||||
contentProvider,
|
||||
drawScaleOffset,
|
||||
tillingContentProvider: tillingContentProvider,
|
||||
offsetContentProvider: offsetContentProvider);
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
_groupItem.ClearTextureIfRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
_groupItem.OnGUI();
|
||||
if (_afterDraw != null && RootItem.PropertyInfoDic.TryGetValue(_texturePropertyName, out ShaderPropertyInfo texturePropertyInfo))
|
||||
{
|
||||
_afterDraw(texturePropertyInfo.Property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TexturePropertyGroupItem : ShaderGUIItem
|
||||
{
|
||||
private readonly TextureObjectItem _textureItem;
|
||||
private readonly ColorLineItem _colorItem;
|
||||
private readonly TextureScaleOffsetItem _scaleOffsetItem;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public TexturePropertyGroupItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string texturePropertyName,
|
||||
string colorPropertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
bool drawScaleOffset = true,
|
||||
Func<bool> isVisible = null,
|
||||
Func<GUIContent> tillingContentProvider = null,
|
||||
Func<GUIContent> offsetContentProvider = null) : base(rootItem, parentItem)
|
||||
{
|
||||
_isVisible = isVisible;
|
||||
_textureItem = new TextureObjectItem(rootItem, this, texturePropertyName, contentProvider);
|
||||
_colorItem = string.IsNullOrEmpty(colorPropertyName)
|
||||
? null
|
||||
: new ColorLineItem(rootItem, this, colorPropertyName, false, contentProvider);
|
||||
_scaleOffsetItem = drawScaleOffset
|
||||
? new TextureScaleOffsetItem(
|
||||
rootItem,
|
||||
this,
|
||||
texturePropertyName,
|
||||
false,
|
||||
tillingContentProvider: tillingContentProvider,
|
||||
offsetContentProvider: offsetContentProvider)
|
||||
: null;
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
_textureItem.ClearTextureIfRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
float singleLineHeight = EditorGUIUtility.singleLineHeight;
|
||||
float rowGap = EditorGUIUtility.standardVerticalSpacing;
|
||||
float textureFieldHeight = 3f * singleLineHeight + 2f * rowGap;
|
||||
float textureContentGap = EditorGUIUtility.standardVerticalSpacing;
|
||||
|
||||
Rect textureGroupRect = ApplyGlobalRectCompensation(LayoutRect(textureFieldHeight));
|
||||
Rect textureLabelRow = new Rect(textureGroupRect.x, textureGroupRect.y, textureGroupRect.width, singleLineHeight);
|
||||
Rect tillingRow = new Rect(textureGroupRect.x, textureGroupRect.y + singleLineHeight + rowGap, textureGroupRect.width, singleLineHeight);
|
||||
Rect offsetRow = new Rect(textureGroupRect.x, tillingRow.y + singleLineHeight + rowGap, textureGroupRect.width, singleLineHeight);
|
||||
Rect indentedTextureLabelRow = ApplyDirectLabelIndentWidth(textureLabelRow);
|
||||
|
||||
Rect textureRect = new Rect(indentedTextureLabelRow.x + 2f, textureLabelRow.y, textureFieldHeight, textureFieldHeight);
|
||||
textureRect.x -= 2f;
|
||||
textureRect.y += 2f;
|
||||
|
||||
float contentX = textureRect.x + textureRect.width + textureContentGap;
|
||||
Rect textureLabelRect = MoveRectLeftKeepingRight(textureLabelRow, contentX);
|
||||
|
||||
SplitLineRect(tillingRow, out _, out Rect tillingVec2Rect, out Rect tillingResetRect, false);
|
||||
Rect tillingLabelRect = MakeLabelRect(tillingRow, contentX, tillingVec2Rect.x);
|
||||
tillingVec2Rect = ApplyControlIndentCompensation(tillingVec2Rect);
|
||||
|
||||
SplitLineRect(offsetRow, out _, out Rect offsetVec2Rect, out Rect offsetResetRect, false);
|
||||
Rect offsetLabelRect = MakeLabelRect(offsetRow, contentX, offsetVec2Rect.x);
|
||||
offsetVec2Rect = ApplyControlIndentCompensation(offsetVec2Rect);
|
||||
|
||||
_textureItem.Draw(textureRect, textureLabelRect);
|
||||
_scaleOffsetItem?.Draw(tillingLabelRect, tillingVec2Rect, tillingResetRect, offsetLabelRect, offsetVec2Rect, offsetResetRect);
|
||||
_colorItem?.OnGUI();
|
||||
}
|
||||
|
||||
public void ClearTextureIfRequested()
|
||||
{
|
||||
_textureItem.ClearTextureIfRequested();
|
||||
}
|
||||
|
||||
private static Rect MoveRectLeftKeepingRight(Rect rect, float x)
|
||||
{
|
||||
float xMax = rect.xMax;
|
||||
rect.x = Mathf.Min(x, xMax);
|
||||
rect.width = xMax - rect.x;
|
||||
return rect;
|
||||
}
|
||||
|
||||
private static Rect MakeLabelRect(Rect rowRect, float labelX, float controlX)
|
||||
{
|
||||
Rect labelRect = rowRect;
|
||||
labelRect.x = Mathf.Min(labelX, controlX);
|
||||
labelRect.width = Mathf.Max(0f, controlX - labelRect.x);
|
||||
return labelRect;
|
||||
}
|
||||
|
||||
private static Rect ApplyControlIndentCompensation(Rect rect)
|
||||
{
|
||||
rect.x -= ControlIndentCompensation;
|
||||
rect.width += ControlIndentCompensation;
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
|
||||
public class TextureObjectItem : ShaderGUIItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public TextureObjectItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string texturePropertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = texturePropertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
ClearTextureIfRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
Draw(ControlRect, LabelRect, ResetRect);
|
||||
DrawBlock();
|
||||
}
|
||||
|
||||
public void Draw(Rect textureRect, Rect labelAndResetRect)
|
||||
{
|
||||
SplitControlAndResetRect(labelAndResetRect, out Rect labelRect, out Rect resetRect, false);
|
||||
Draw(textureRect, labelRect, resetRect);
|
||||
}
|
||||
|
||||
public void Draw(Rect textureRect, Rect labelRect, Rect resetRect)
|
||||
{
|
||||
ControlRect = textureRect;
|
||||
LabelRect = labelRect;
|
||||
ResetRect = resetRect;
|
||||
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
if (RootItem.ClearUnusedTextureReferencesRequested && (IsParentControlDisabled || !GUI.enabled))
|
||||
{
|
||||
ClearTextureIfRequested();
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
GUI.Label(LabelRect, GuiContent, EditorStyles.boldLabel);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, property);
|
||||
Texture texture;
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
texture = (Texture)EditorGUI.ObjectField(ControlRect, property.textureValue, typeof(Texture2D), false);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.textureValue = texture;
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
public void ClearTextureIfRequested()
|
||||
{
|
||||
if (!RootItem.ClearUnusedTextureReferencesRequested || PropertyInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
if (property == null || property.textureValue == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
property.textureValue = null;
|
||||
OnEndChange();
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (!isCallByChild)
|
||||
{
|
||||
bool isDefaultValue = PropertyInfo == null ||
|
||||
(!PropertyInfo.Property.hasMixedValue && PropertyInfo.Property.textureValue == null);
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (ShaderGUIItem childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
if (PropertyInfo != null)
|
||||
{
|
||||
PropertyInfo.Property.textureValue = null;
|
||||
PropertyIsDefaultValue = true;
|
||||
}
|
||||
|
||||
HasModified = false;
|
||||
if (!isCallByParent)
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TextureScaleOffsetItem : ShaderGUIItem
|
||||
{
|
||||
private static readonly GUIContent TillingContent = new GUIContent("Tilling");
|
||||
private static readonly GUIContent OffsetContent = new GUIContent("Offset");
|
||||
private static readonly Vector4 DefaultScaleOffset = new Vector4(1f, 1f, 0f, 0f);
|
||||
|
||||
private readonly bool _isVectorProperty;
|
||||
private readonly Func<bool> _isVisible;
|
||||
private readonly Func<GUIContent> _tillingContentProvider;
|
||||
private readonly Func<GUIContent> _offsetContentProvider;
|
||||
private readonly GUIContent _tillingContent;
|
||||
private readonly GUIContent _offsetContent;
|
||||
|
||||
public TextureScaleOffsetItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
bool isVectorProperty,
|
||||
Func<bool> isVisible = null,
|
||||
Func<GUIContent> tillingContentProvider = null,
|
||||
Func<GUIContent> offsetContentProvider = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_isVectorProperty = isVectorProperty;
|
||||
_isVisible = isVisible;
|
||||
_tillingContentProvider = tillingContentProvider ?? (() => TillingContent);
|
||||
_offsetContentProvider = offsetContentProvider ?? (() => OffsetContent);
|
||||
_tillingContent = _tillingContentProvider();
|
||||
_offsetContent = _offsetContentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
Rect tillingLabelRect = LabelRect;
|
||||
Rect tillingVec2Rect = ControlRect;
|
||||
Rect tillingResetRect = ResetRect;
|
||||
|
||||
GetRect();
|
||||
Rect offsetLabelRect = LabelRect;
|
||||
Rect offsetVec2Rect = ControlRect;
|
||||
Rect offsetResetRect = ResetRect;
|
||||
|
||||
Draw(tillingLabelRect, tillingVec2Rect, tillingResetRect, offsetLabelRect, offsetVec2Rect, offsetResetRect, true);
|
||||
DrawBlock();
|
||||
}
|
||||
|
||||
public void Draw(
|
||||
Rect tillingLabelRect,
|
||||
Rect tillingVec2Rect,
|
||||
Rect tillingResetRect,
|
||||
Rect offsetLabelRect,
|
||||
Rect offsetVec2Rect,
|
||||
Rect offsetResetRect,
|
||||
bool useEditorLabelField = false)
|
||||
{
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
Vector4 scaleOffset = GetScaleOffset(property);
|
||||
|
||||
DrawLabel(tillingLabelRect, _tillingContent, useEditorLabelField);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
Vector2 tilling = new Vector2(scaleOffset.x, scaleOffset.y);
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool tillingAnimatedScope = BeginAnimatedPropertyBackground(tillingVec2Rect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
tilling = EditorGUI.Vector2Field(tillingVec2Rect, GUIContent.none, tilling);
|
||||
}
|
||||
EndAnimatedPropertyBackground(tillingAnimatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
scaleOffset.x = tilling.x;
|
||||
scaleOffset.y = tilling.y;
|
||||
Apply(scaleOffset);
|
||||
}
|
||||
|
||||
bool tillingModified = property.hasMixedValue || tilling != Vector2.one;
|
||||
if (GUI.Button(tillingResetRect, tillingModified ? "R" : string.Empty, tillingModified ? GUI.skin.button : GUI.skin.label))
|
||||
{
|
||||
scaleOffset.x = 1f;
|
||||
scaleOffset.y = 1f;
|
||||
Apply(scaleOffset);
|
||||
}
|
||||
}
|
||||
|
||||
DrawLabel(offsetLabelRect, _offsetContent, useEditorLabelField);
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
Vector2 offset = new Vector2(scaleOffset.z, scaleOffset.w);
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool offsetAnimatedScope = BeginAnimatedPropertyBackground(offsetVec2Rect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
offset = EditorGUI.Vector2Field(offsetVec2Rect, GUIContent.none, offset);
|
||||
}
|
||||
EndAnimatedPropertyBackground(offsetAnimatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
scaleOffset.z = offset.x;
|
||||
scaleOffset.w = offset.y;
|
||||
Apply(scaleOffset);
|
||||
}
|
||||
|
||||
bool offsetModified = property.hasMixedValue || offset != Vector2.zero;
|
||||
if (GUI.Button(offsetResetRect, offsetModified ? "R" : string.Empty, offsetModified ? GUI.skin.button : GUI.skin.label))
|
||||
{
|
||||
scaleOffset.z = 0f;
|
||||
scaleOffset.w = 0f;
|
||||
Apply(scaleOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLabel(Rect rect, GUIContent content, bool useEditorLabelField)
|
||||
{
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
if (useEditorLabelField)
|
||||
{
|
||||
EditorGUI.LabelField(rect, content);
|
||||
return;
|
||||
}
|
||||
|
||||
GUI.Label(rect, content);
|
||||
}
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (!isCallByChild)
|
||||
{
|
||||
bool isDefaultValue = true;
|
||||
if (PropertyInfo != null)
|
||||
{
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
isDefaultValue = !property.hasMixedValue && Approximately(GetScaleOffset(property), DefaultScaleOffset);
|
||||
}
|
||||
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (ShaderGUIItem childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
if (PropertyInfo != null)
|
||||
{
|
||||
SetScaleOffset(PropertyInfo.Property, DefaultScaleOffset);
|
||||
PropertyIsDefaultValue = true;
|
||||
}
|
||||
|
||||
HasModified = false;
|
||||
if (!isCallByParent)
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector4 GetScaleOffset(MaterialProperty property)
|
||||
{
|
||||
return _isVectorProperty ? property.vectorValue : property.textureScaleAndOffset;
|
||||
}
|
||||
|
||||
private void Apply(Vector4 scaleOffset)
|
||||
{
|
||||
SetScaleOffset(PropertyInfo.Property, scaleOffset);
|
||||
OnEndChange();
|
||||
}
|
||||
|
||||
private void SetScaleOffset(MaterialProperty property, Vector4 scaleOffset)
|
||||
{
|
||||
if (_isVectorProperty)
|
||||
{
|
||||
property.vectorValue = scaleOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
property.textureScaleAndOffset = scaleOffset;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f026fa752b843a0b469f9306b2ff461
|
||||
timeCreated: 1777032000
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class ToggleItem : ShaderGUIItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
private readonly Action<bool> _onValueChanged;
|
||||
|
||||
public ToggleItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Action<bool> onValueChanged = null,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_onValueChanged = onValueChanged;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnGUI();
|
||||
}
|
||||
|
||||
public override void DrawController()
|
||||
{
|
||||
bool value = PropertyInfo.Property.floatValue > 0.5f;
|
||||
value = EditorGUI.Toggle(ControlRect, value);
|
||||
SetFloatIfDifferent(PropertyInfo.Property, value ? 1f : 0f);
|
||||
}
|
||||
|
||||
public override void OnEndChange()
|
||||
{
|
||||
base.OnEndChange();
|
||||
_onValueChanged?.Invoke(PropertyInfo.Property.floatValue > 0.5f);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
base.ExecuteReset(isCallByParent);
|
||||
_onValueChanged?.Invoke(PropertyInfo.Property.floatValue > 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afba064c4c70401bbb7133de835d2d35
|
||||
timeCreated: 1777032000
|
||||
@@ -0,0 +1,610 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBShaderEditor
|
||||
{
|
||||
public class Vector2LineItem : ShaderGUIItem
|
||||
{
|
||||
private readonly bool _firstLine;
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public Vector2LineItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
bool firstLine,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_firstLine = firstLine;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Vector4 vector = property.vectorValue;
|
||||
Vector2 value = _firstLine ? new Vector2(vector.x, vector.y) : new Vector2(vector.z, vector.w);
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
value = EditorGUI.Vector2Field(ControlRect, GUIContent.none, value);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (_firstLine)
|
||||
{
|
||||
vector.x = value.x;
|
||||
vector.y = value.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
vector.z = value.x;
|
||||
vector.w = value.y;
|
||||
}
|
||||
|
||||
property.vectorValue = vector;
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (PropertyInfo == null)
|
||||
{
|
||||
base.CheckIsPropertyModified(isCallByChild);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isCallByChild)
|
||||
{
|
||||
Vector4 defaultValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
Vector4 currentValue = PropertyInfo.Property.vectorValue;
|
||||
bool isDefaultValue = _firstLine
|
||||
? Mathf.Approximately(currentValue.x, defaultValue.x) && Mathf.Approximately(currentValue.y, defaultValue.y)
|
||||
: Mathf.Approximately(currentValue.z, defaultValue.z) && Mathf.Approximately(currentValue.w, defaultValue.w);
|
||||
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (var childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
Vector4 defaultValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
Vector4 currentValue = PropertyInfo.Property.vectorValue;
|
||||
if (_firstLine)
|
||||
{
|
||||
currentValue.x = defaultValue.x;
|
||||
currentValue.y = defaultValue.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentValue.z = defaultValue.z;
|
||||
currentValue.w = defaultValue.w;
|
||||
}
|
||||
|
||||
PropertyInfo.Property.vectorValue = currentValue;
|
||||
PropertyIsDefaultValue = true;
|
||||
HasModified = false;
|
||||
if (!isCallByParent)
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class VectorComponentItem : ShaderGUIItem
|
||||
{
|
||||
private readonly int _componentIndex;
|
||||
private readonly bool _isSlider;
|
||||
private readonly float _min;
|
||||
private readonly float _max;
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
|
||||
public VectorComponentItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
int componentIndex,
|
||||
Func<GUIContent> contentProvider,
|
||||
bool isSlider,
|
||||
float min = 0f,
|
||||
float max = 1f,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_componentIndex = componentIndex;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isSlider = isSlider;
|
||||
_min = min;
|
||||
_max = max;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Vector4 vector = property.vectorValue;
|
||||
float value = GetValue(vector);
|
||||
value = _isSlider
|
||||
? DraggableLabelFloat.Handle(
|
||||
LabelRect,
|
||||
value,
|
||||
DraggableLabelFloat.GetSensitivityByRange(_min, _max),
|
||||
_min,
|
||||
_max)
|
||||
: DraggableLabelFloat.Handle(LabelRect, value);
|
||||
if (_isSlider)
|
||||
{
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
value = EditorGUI.Slider(ControlRect, value, _min, _max);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
value = EditorGUI.FloatField(ControlRect, value);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
}
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
SetValue(ref vector, value);
|
||||
property.vectorValue = vector;
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
private float GetValue(Vector4 vector)
|
||||
{
|
||||
switch (_componentIndex)
|
||||
{
|
||||
case 0: return vector.x;
|
||||
case 1: return vector.y;
|
||||
case 2: return vector.z;
|
||||
case 3: return vector.w;
|
||||
default: return vector.x;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValue(ref Vector4 vector, float value)
|
||||
{
|
||||
switch (_componentIndex)
|
||||
{
|
||||
case 0:
|
||||
vector.x = value;
|
||||
break;
|
||||
case 1:
|
||||
vector.y = value;
|
||||
break;
|
||||
case 2:
|
||||
vector.z = value;
|
||||
break;
|
||||
case 3:
|
||||
vector.w = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (PropertyInfo == null)
|
||||
{
|
||||
base.CheckIsPropertyModified(isCallByChild);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isCallByChild)
|
||||
{
|
||||
Vector4 defaultValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
bool isDefaultValue = Mathf.Approximately(GetValue(PropertyInfo.Property.vectorValue), GetValue(defaultValue));
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (var childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
Vector4 vector = PropertyInfo.Property.vectorValue;
|
||||
Vector4 defaultValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
SetValue(ref vector, GetValue(defaultValue));
|
||||
PropertyInfo.Property.vectorValue = vector;
|
||||
PropertyIsDefaultValue = true;
|
||||
HasModified = false;
|
||||
if (!isCallByParent)
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class VectorComponentRangeSliderItem : ShaderGUIItem
|
||||
{
|
||||
private readonly int _componentIndex;
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly string _rangePropertyName;
|
||||
private readonly Func<bool> _isVisible;
|
||||
private ShaderPropertyInfo _rangePropertyInfo;
|
||||
|
||||
private const float SliderFrontGap = 5f;
|
||||
private const float SliderBackGap = 2f;
|
||||
|
||||
public VectorComponentRangeSliderItem(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
int componentIndex,
|
||||
string rangePropertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_componentIndex = componentIndex;
|
||||
_rangePropertyName = rangePropertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void InitTriggerByChild()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_rangePropertyName))
|
||||
{
|
||||
_rangePropertyInfo = RootItem.PropertyInfoDic[_rangePropertyName];
|
||||
}
|
||||
|
||||
base.InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
DrawController();
|
||||
}
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
OnEndChange();
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
public override void DrawController()
|
||||
{
|
||||
Vector4 vector = PropertyInfo.Property.vectorValue;
|
||||
Vector4 range = _rangePropertyInfo.Property.vectorValue;
|
||||
float min = range.x;
|
||||
float max = range.y;
|
||||
float sliderMin = Mathf.Min(min, max);
|
||||
float sliderMax = Mathf.Max(min, max);
|
||||
float value = GetValue(vector);
|
||||
|
||||
float rangeFieldWidth = EditorGUIUtility.fieldWidth;
|
||||
Rect minRect = ControlRect;
|
||||
minRect.width = rangeFieldWidth;
|
||||
Rect maxRect = ControlRect;
|
||||
maxRect.x = ControlRect.xMax - rangeFieldWidth;
|
||||
maxRect.width = rangeFieldWidth;
|
||||
Rect sliderRect = ControlRect;
|
||||
sliderRect.x = minRect.xMax + SliderFrontGap;
|
||||
sliderRect.width = Mathf.Max(0f, maxRect.x - sliderRect.x - SliderBackGap + EditorGUI.indentLevel * UnityEditorGUIIndentWidth);
|
||||
|
||||
RangeVecHasMixedValue(out bool minValueHasMixed, out bool maxValueHasMixed);
|
||||
|
||||
EditorGUI.showMixedValue = minValueHasMixed;
|
||||
bool minAnimatedScope = BeginAnimatedPropertyBackground(minRect, _rangePropertyInfo.Property);
|
||||
min = EditorGUI.FloatField(minRect, min);
|
||||
EndAnimatedPropertyBackground(minAnimatedScope);
|
||||
|
||||
EditorGUI.showMixedValue = maxValueHasMixed;
|
||||
bool maxAnimatedScope = BeginAnimatedPropertyBackground(maxRect, _rangePropertyInfo.Property);
|
||||
max = EditorGUI.FloatField(maxRect, max);
|
||||
EndAnimatedPropertyBackground(maxAnimatedScope);
|
||||
|
||||
range.x = min;
|
||||
range.y = max;
|
||||
SetVectorIfDifferent(_rangePropertyInfo.Property, range);
|
||||
|
||||
sliderMin = Mathf.Min(min, max);
|
||||
sliderMax = Mathf.Max(min, max);
|
||||
value = DraggableLabelFloat.Handle(
|
||||
LabelRect,
|
||||
value,
|
||||
DraggableLabelFloat.GetSensitivityByRange(sliderMin, sliderMax),
|
||||
sliderMin,
|
||||
sliderMax);
|
||||
EditorGUI.showMixedValue = PropertyInfo.Property.hasMixedValue;
|
||||
bool sliderAnimatedScope = BeginAnimatedPropertyBackground(sliderRect, PropertyInfo.Property);
|
||||
value = SliderNoIndent(sliderRect, value, sliderMin, sliderMax);
|
||||
EndAnimatedPropertyBackground(sliderAnimatedScope);
|
||||
|
||||
SetValue(ref vector, Mathf.Clamp(value, sliderMin, sliderMax));
|
||||
SetVectorIfDifferent(PropertyInfo.Property, vector);
|
||||
EditorGUI.showMixedValue = false;
|
||||
}
|
||||
|
||||
private static float SliderNoIndent(Rect rect, float value, float min, float max)
|
||||
{
|
||||
int indentLevel = EditorGUI.indentLevel;
|
||||
try
|
||||
{
|
||||
EditorGUI.indentLevel = 0;
|
||||
return EditorGUI.Slider(rect, value, min, max);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorGUI.indentLevel = indentLevel;
|
||||
}
|
||||
}
|
||||
|
||||
private void RangeVecHasMixedValue(out bool minValueHasMixed, out bool maxValueHasMixed)
|
||||
{
|
||||
minValueHasMixed = false;
|
||||
maxValueHasMixed = false;
|
||||
if (RootItem.Mats.Count <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float firstMin = 0f;
|
||||
float firstMax = 0f;
|
||||
for (int i = 0; i < RootItem.Mats.Count; i++)
|
||||
{
|
||||
Vector4 range = RootItem.Mats[i].GetVector(_rangePropertyName);
|
||||
if (i == 0)
|
||||
{
|
||||
firstMin = range.x;
|
||||
firstMax = range.y;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Mathf.Approximately(firstMin, range.x))
|
||||
{
|
||||
minValueHasMixed = true;
|
||||
}
|
||||
|
||||
if (!Mathf.Approximately(firstMax, range.y))
|
||||
{
|
||||
maxValueHasMixed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float GetValue(Vector4 vector)
|
||||
{
|
||||
switch (_componentIndex)
|
||||
{
|
||||
case 0: return vector.x;
|
||||
case 1: return vector.y;
|
||||
case 2: return vector.z;
|
||||
case 3: return vector.w;
|
||||
default: return vector.x;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValue(ref Vector4 vector, float value)
|
||||
{
|
||||
switch (_componentIndex)
|
||||
{
|
||||
case 0:
|
||||
vector.x = value;
|
||||
break;
|
||||
case 1:
|
||||
vector.y = value;
|
||||
break;
|
||||
case 2:
|
||||
vector.z = value;
|
||||
break;
|
||||
case 3:
|
||||
vector.w = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
||||
{
|
||||
if (!isCallByChild)
|
||||
{
|
||||
Vector4 defaultValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
Vector4 defaultRange = RootItem.Shader.GetPropertyDefaultVectorValue(_rangePropertyInfo.Index);
|
||||
bool isDefaultValue = Mathf.Approximately(GetValue(PropertyInfo.Property.vectorValue), GetValue(defaultValue)) &&
|
||||
_rangePropertyInfo.Property.vectorValue == defaultRange;
|
||||
if (isDefaultValue == PropertyIsDefaultValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyIsDefaultValue = isDefaultValue;
|
||||
}
|
||||
|
||||
HasModified = !PropertyIsDefaultValue;
|
||||
foreach (ShaderGUIItem childItem in ChildrenItemList)
|
||||
{
|
||||
HasModified |= childItem.HasModified;
|
||||
}
|
||||
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
Vector4 vector = PropertyInfo.Property.vectorValue;
|
||||
Vector4 defaultValue = RootItem.Shader.GetPropertyDefaultVectorValue(PropertyInfo.Index);
|
||||
SetValue(ref vector, GetValue(defaultValue));
|
||||
PropertyInfo.Property.vectorValue = vector;
|
||||
_rangePropertyInfo.Property.vectorValue = RootItem.Shader.GetPropertyDefaultVectorValue(_rangePropertyInfo.Index);
|
||||
|
||||
PropertyIsDefaultValue = true;
|
||||
HasModified = false;
|
||||
if (!isCallByParent)
|
||||
{
|
||||
ParentItem?.CheckIsPropertyModified(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Vector3Item : ShaderGUIItem
|
||||
{
|
||||
private readonly Func<GUIContent> _contentProvider;
|
||||
private readonly Func<bool> _isVisible;
|
||||
private readonly Action<Vector3> _onValueChanged;
|
||||
|
||||
public Vector3Item(
|
||||
ShaderGUIRootItem rootItem,
|
||||
ShaderGUIItem parentItem,
|
||||
string propertyName,
|
||||
Func<GUIContent> contentProvider,
|
||||
Action<Vector3> onValueChanged = null,
|
||||
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
||||
_onValueChanged = onValueChanged;
|
||||
_isVisible = isVisible;
|
||||
GuiContent = _contentProvider();
|
||||
InitTriggerByChild();
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (_isVisible != null && !_isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetRect();
|
||||
MaterialProperty property = PropertyInfo.Property;
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.LabelField(LabelRect, GuiContent);
|
||||
}
|
||||
|
||||
using (ParentControlDisabledScope())
|
||||
{
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Vector4 vector = property.vectorValue;
|
||||
Vector3 value = new Vector3(vector.x, vector.y, vector.z);
|
||||
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, property);
|
||||
using (new EditorGUIIndentLevelScope(0))
|
||||
{
|
||||
value = EditorGUI.Vector3Field(ControlRect, GUIContent.none, value);
|
||||
}
|
||||
EndAnimatedPropertyBackground(animatedScope);
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
vector.x = value.x;
|
||||
vector.y = value.y;
|
||||
vector.z = value.z;
|
||||
property.vectorValue = vector;
|
||||
OnEndChange();
|
||||
_onValueChanged?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
|
||||
public override void ExecuteReset(bool isCallByParent = false)
|
||||
{
|
||||
base.ExecuteReset(isCallByParent);
|
||||
Vector4 vector = PropertyInfo.Property.vectorValue;
|
||||
_onValueChanged?.Invoke(new Vector3(vector.x, vector.y, vector.z));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20a960b448954fc29c9197c2357e89ec
|
||||
timeCreated: 1777032000
|
||||
Reference in New Issue
Block a user