1210 lines
44 KiB
C#
1210 lines
44 KiB
C#
using System;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using NBShader;
|
|
|
|
namespace NBShaderEditor
|
|
{
|
|
public class PropertyToggleBlockItem : ShaderGUIItem
|
|
{
|
|
private readonly string _foldOutPropertyName;
|
|
private readonly Func<GUIContent> _contentProvider;
|
|
private readonly int _flagBits;
|
|
private readonly int _flagIndex;
|
|
private readonly string _keyword;
|
|
private readonly string _shaderPassName;
|
|
private readonly Action<bool> _onValueChanged;
|
|
private readonly Func<bool> _isVisible;
|
|
private readonly GUIStyle _labelStyle;
|
|
private readonly ShaderGUIFoldOutHelper _foldOutHelper;
|
|
|
|
public PropertyToggleBlockItem(
|
|
NBShaderRootItem rootItem,
|
|
ShaderGUIItem parentItem,
|
|
string foldOutPropertyName,
|
|
string togglePropertyName,
|
|
Func<GUIContent> contentProvider,
|
|
int flagBits = 0,
|
|
int flagIndex = 0,
|
|
string keyword = null,
|
|
string shaderPassName = null,
|
|
Action<bool> onValueChanged = null,
|
|
Func<bool> isVisible = null,
|
|
bool bold = false) : base(rootItem, parentItem)
|
|
{
|
|
_foldOutPropertyName = foldOutPropertyName;
|
|
PropertyName = togglePropertyName;
|
|
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
|
_flagBits = flagBits;
|
|
_flagIndex = flagIndex;
|
|
_keyword = keyword;
|
|
_shaderPassName = shaderPassName;
|
|
_onValueChanged = onValueChanged;
|
|
_isVisible = isVisible;
|
|
_labelStyle = new GUIStyle(EditorStyles.label)
|
|
{
|
|
fontStyle = bold ? FontStyle.Bold : FontStyle.Normal
|
|
};
|
|
_foldOutHelper = new ShaderGUIFoldOutHelper(rootItem, foldOutPropertyName);
|
|
GuiContent = _contentProvider();
|
|
InitTriggerByChild();
|
|
}
|
|
|
|
public override void OnGUI()
|
|
{
|
|
if (!FeatureToggleFoldOutItem.IsTierKeywordAllowed(RootItem as NBShaderRootItem, _keyword))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_isVisible != null && !_isVisible())
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetRect();
|
|
MaterialProperty property = PropertyInfo.Property;
|
|
|
|
bool enabled = property.floatValue > 0.5f;
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.showMixedValue = property.hasMixedValue;
|
|
EditorGUI.BeginChangeCheck();
|
|
bool animatedScope = BeginAnimatedPropertyBackground(ControlRect, property);
|
|
using (new EditorGUIIndentLevelScope(0))
|
|
{
|
|
enabled = EditorGUI.Toggle(ControlRect, enabled);
|
|
}
|
|
EndAnimatedPropertyBackground(animatedScope);
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
property.floatValue = enabled ? 1f : 0f;
|
|
ApplySideEffects(enabled);
|
|
OnEndChange();
|
|
}
|
|
}
|
|
|
|
Rect foldOutRect = LabelRect;
|
|
foldOutRect.width = Mathf.Max(0f, ControlRect.x - LabelRect.x);
|
|
_foldOutHelper.DrawFoldOut(foldOutRect);
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.LabelField(LabelRect, GuiContent, _labelStyle);
|
|
}
|
|
|
|
DrawResetButton();
|
|
|
|
if (_foldOutHelper.BeginFadeGroup())
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
using (new InheritedControlDisabledScope(property.hasMixedValue || property.floatValue <= 0.5f))
|
|
{
|
|
DrawBlock();
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
_foldOutHelper.EndFadedGroup();
|
|
}
|
|
|
|
public override void ExecuteReset(bool isCallByParent = false)
|
|
{
|
|
base.ExecuteReset(isCallByParent);
|
|
ApplySideEffects(PropertyInfo.Property.floatValue > 0.5f);
|
|
}
|
|
|
|
public override void DrawBlock()
|
|
{
|
|
for (int i = 0; i < ChildrenItemList.Count; i++)
|
|
{
|
|
ChildrenItemList[i].OnGUI();
|
|
}
|
|
}
|
|
|
|
private void ApplySideEffects(bool enabled)
|
|
{
|
|
if (RootItem is NBShaderRootItem nbRootItem)
|
|
{
|
|
if (_flagBits != 0)
|
|
{
|
|
nbRootItem.SyncService.ApplyToggleFlag(_flagBits, enabled, _flagIndex);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(_keyword))
|
|
{
|
|
nbRootItem.SyncService.ApplyToggleKeyword(_keyword, enabled);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(_shaderPassName))
|
|
{
|
|
nbRootItem.SyncService.ApplyShaderPass(_shaderPassName, enabled);
|
|
}
|
|
}
|
|
|
|
_onValueChanged?.Invoke(enabled);
|
|
}
|
|
}
|
|
|
|
public class NBShaderKeywordToggleItem : ToggleItem
|
|
{
|
|
public NBShaderKeywordToggleItem(
|
|
NBShaderRootItem rootItem,
|
|
ShaderGUIItem parentItem,
|
|
string propertyName,
|
|
string keyword,
|
|
Func<GUIContent> contentProvider,
|
|
Action<bool> onValueChanged = null,
|
|
Func<bool> isVisible = null)
|
|
: base(
|
|
rootItem,
|
|
parentItem,
|
|
propertyName,
|
|
contentProvider,
|
|
enabled =>
|
|
{
|
|
rootItem.SyncService.ApplyToggleKeyword(keyword, enabled);
|
|
onValueChanged?.Invoke(enabled);
|
|
},
|
|
FeatureToggleFoldOutItem.TierVisible(rootItem, keyword, isVisible))
|
|
{
|
|
}
|
|
}
|
|
|
|
public class TextureRelatedFoldOutItem : ShaderGUIItem
|
|
{
|
|
private readonly ShaderGUIFoldOutHelper _foldOutHelper;
|
|
private readonly string _texturePropertyName;
|
|
private readonly Func<GUIContent> _contentProvider;
|
|
private readonly Func<bool> _isVisible;
|
|
|
|
public TextureRelatedFoldOutItem(
|
|
ShaderGUIRootItem rootItem,
|
|
ShaderGUIItem parentItem,
|
|
string foldOutPropertyName,
|
|
string texturePropertyName,
|
|
Func<GUIContent> contentProvider,
|
|
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
|
{
|
|
_foldOutHelper = new ShaderGUIFoldOutHelper(rootItem, foldOutPropertyName);
|
|
_texturePropertyName = texturePropertyName;
|
|
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
|
_isVisible = isVisible;
|
|
GuiContent = _contentProvider();
|
|
CheckIsPropertyModified();
|
|
}
|
|
|
|
public override void OnGUI()
|
|
{
|
|
if (_isVisible != null && !_isVisible())
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetRect();
|
|
_foldOutHelper.DrawFoldOut(LabelRect);
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.LabelField(LabelRect, GuiContent, EditorStyles.boldLabel);
|
|
}
|
|
|
|
DrawResetButton();
|
|
|
|
if (_foldOutHelper.BeginFadeGroup())
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
using (new InheritedControlDisabledScope(!HasTexture()))
|
|
{
|
|
DrawBlock();
|
|
}
|
|
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
_foldOutHelper.EndFadedGroup();
|
|
}
|
|
|
|
public override void DrawBlock()
|
|
{
|
|
for (int i = 0; i < ChildrenItemList.Count; i++)
|
|
{
|
|
ChildrenItemList[i].OnGUI();
|
|
}
|
|
}
|
|
|
|
private bool HasTexture()
|
|
{
|
|
return RootItem.PropertyInfoDic.TryGetValue(_texturePropertyName, out ShaderPropertyInfo info) &&
|
|
(info.Property.hasMixedValue || info.Property.textureValue != null);
|
|
}
|
|
}
|
|
|
|
public class ColorChannelSelectItem : ShaderGUIItem
|
|
{
|
|
private static readonly string[] ChannelNames = { "R", "G", "B", "A" };
|
|
private readonly Func<GUIContent> _contentProvider;
|
|
private readonly int _colorChannelFlagPos;
|
|
private readonly int _defaultChannel;
|
|
private readonly Func<bool> _isVisible;
|
|
|
|
public ColorChannelSelectItem(
|
|
ShaderGUIRootItem rootItem,
|
|
ShaderGUIItem parentItem,
|
|
int colorChannelFlagPos,
|
|
int defaultChannel,
|
|
Func<GUIContent> contentProvider,
|
|
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
|
{
|
|
_colorChannelFlagPos = colorChannelFlagPos;
|
|
_defaultChannel = Mathf.Clamp(defaultChannel, 0, 3);
|
|
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
|
_isVisible = isVisible;
|
|
GuiContent = _contentProvider();
|
|
CheckIsPropertyModified();
|
|
}
|
|
|
|
public override void OnGUI()
|
|
{
|
|
if (_isVisible != null && !_isVisible())
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetRect();
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.LabelField(LabelRect, GuiContent);
|
|
}
|
|
|
|
int channel = GetFirstChannel();
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.showMixedValue = HasMixedValue();
|
|
EditorGUI.BeginChangeCheck();
|
|
using (new EditorGUIIndentLevelScope(0))
|
|
{
|
|
channel = EditorGUI.Popup(
|
|
ControlRect,
|
|
channel,
|
|
NBShaderInspectorLocalization.GetInspectorOptions("protocol.colorChannel", ChannelNames));
|
|
}
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
SetChannel(channel);
|
|
CheckIsPropertyModified();
|
|
}
|
|
}
|
|
|
|
DrawResetButton();
|
|
}
|
|
|
|
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
|
{
|
|
PropertyIsDefaultValue = !HasMixedValue() && GetFirstChannel() == _defaultChannel;
|
|
HasModified = !PropertyIsDefaultValue;
|
|
ParentItem?.CheckIsPropertyModified(true);
|
|
}
|
|
|
|
public override void ExecuteReset(bool isCallByParent = false)
|
|
{
|
|
SetChannel(_defaultChannel);
|
|
PropertyIsDefaultValue = true;
|
|
HasModified = false;
|
|
if (!isCallByParent)
|
|
{
|
|
ParentItem?.CheckIsPropertyModified(true);
|
|
}
|
|
}
|
|
|
|
private int GetFirstChannel()
|
|
{
|
|
NBShaderFlags flags = GetFlags(0);
|
|
return flags == null ? _defaultChannel : (int)flags.GetColorChanel(_colorChannelFlagPos);
|
|
}
|
|
|
|
private bool HasMixedValue()
|
|
{
|
|
int first = GetFirstChannel();
|
|
for (int i = 1; i < RootItem.ShaderFlags.Count; i++)
|
|
{
|
|
NBShaderFlags flags = GetFlags(i);
|
|
if (flags != null && (int)flags.GetColorChanel(_colorChannelFlagPos) != first)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void SetChannel(int channel)
|
|
{
|
|
for (int i = 0; i < RootItem.ShaderFlags.Count; i++)
|
|
{
|
|
GetFlags(i)?.SetColorChanel((NBShaderFlags.ColorChannel)channel, _colorChannelFlagPos);
|
|
}
|
|
}
|
|
|
|
private NBShaderFlags GetFlags(int index)
|
|
{
|
|
return index >= 0 &&
|
|
index < RootItem.ShaderFlags.Count &&
|
|
RootItem.ShaderFlags[index] is NBShaderFlags flags
|
|
? flags
|
|
: null;
|
|
}
|
|
}
|
|
|
|
public class CustomDataSelectItem : ShaderGUIItem
|
|
{
|
|
private static readonly string[] Options =
|
|
{
|
|
"关闭",
|
|
"CustomData1.x",
|
|
"CustomData1.y",
|
|
"CustomData1.z",
|
|
"CustomData1.w",
|
|
"CustomData2.x",
|
|
"CustomData2.y",
|
|
"CustomData2.z",
|
|
"CustomData2.w"
|
|
};
|
|
|
|
private readonly Func<GUIContent> _contentProvider;
|
|
private readonly int _dataBitPos;
|
|
private readonly int _dataIndex;
|
|
private readonly Func<bool> _isVisible;
|
|
|
|
public CustomDataSelectItem(
|
|
ShaderGUIRootItem rootItem,
|
|
ShaderGUIItem parentItem,
|
|
int dataBitPos,
|
|
int dataIndex,
|
|
Func<GUIContent> contentProvider,
|
|
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
|
{
|
|
_dataBitPos = dataBitPos;
|
|
_dataIndex = dataIndex;
|
|
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
|
_isVisible = isVisible;
|
|
GuiContent = _contentProvider();
|
|
CheckIsPropertyModified();
|
|
}
|
|
|
|
public override void OnGUI()
|
|
{
|
|
if (_isVisible != null && !_isVisible())
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetRect();
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.LabelField(LabelRect, GuiContent);
|
|
}
|
|
|
|
NBShaderFlags.CutomDataComponent component = GetFirstComponent();
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.showMixedValue = HasMixedValue();
|
|
EditorGUI.BeginChangeCheck();
|
|
int index;
|
|
using (new EditorGUIIndentLevelScope(0))
|
|
{
|
|
index = EditorGUI.Popup(
|
|
ControlRect,
|
|
(int)component,
|
|
NBShaderInspectorLocalization.GetInspectorOptions("protocol.customData", Options));
|
|
}
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
SetComponent((NBShaderFlags.CutomDataComponent)index);
|
|
CheckIsPropertyModified();
|
|
if (RootItem is NBShaderRootItem nbRootItem)
|
|
{
|
|
nbRootItem.SyncService.SyncMaterialState();
|
|
}
|
|
}
|
|
}
|
|
|
|
DrawResetButton();
|
|
}
|
|
|
|
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
|
{
|
|
PropertyIsDefaultValue = !HasMixedValue() && GetFirstComponent() == NBShaderFlags.CutomDataComponent.Off;
|
|
HasModified = !PropertyIsDefaultValue;
|
|
ParentItem?.CheckIsPropertyModified(true);
|
|
}
|
|
|
|
public override void ExecuteReset(bool isCallByParent = false)
|
|
{
|
|
SetComponent(NBShaderFlags.CutomDataComponent.Off);
|
|
if (RootItem is NBShaderRootItem nbRootItem)
|
|
{
|
|
nbRootItem.SyncService.SyncMaterialState();
|
|
}
|
|
|
|
PropertyIsDefaultValue = true;
|
|
HasModified = false;
|
|
if (!isCallByParent)
|
|
{
|
|
ParentItem?.CheckIsPropertyModified(true);
|
|
}
|
|
}
|
|
|
|
private NBShaderFlags.CutomDataComponent GetFirstComponent()
|
|
{
|
|
NBShaderFlags flags = GetFlags(0);
|
|
return flags == null ? NBShaderFlags.CutomDataComponent.Off : flags.GetCustomDataFlag(_dataBitPos, _dataIndex);
|
|
}
|
|
|
|
private bool HasMixedValue()
|
|
{
|
|
NBShaderFlags.CutomDataComponent first = GetFirstComponent();
|
|
for (int i = 1; i < RootItem.ShaderFlags.Count; i++)
|
|
{
|
|
NBShaderFlags flags = GetFlags(i);
|
|
if (flags != null && flags.GetCustomDataFlag(_dataBitPos, _dataIndex) != first)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void SetComponent(NBShaderFlags.CutomDataComponent component)
|
|
{
|
|
for (int i = 0; i < RootItem.ShaderFlags.Count; i++)
|
|
{
|
|
GetFlags(i)?.SetCustomDataFlag(component, _dataBitPos, _dataIndex);
|
|
}
|
|
}
|
|
|
|
private NBShaderFlags GetFlags(int index)
|
|
{
|
|
return index >= 0 &&
|
|
index < RootItem.ShaderFlags.Count &&
|
|
RootItem.ShaderFlags[index] is NBShaderFlags flags
|
|
? flags
|
|
: null;
|
|
}
|
|
}
|
|
|
|
public class WrapModeItem : ShaderGUIItem
|
|
{
|
|
private static readonly string[] Options =
|
|
{
|
|
"Repeat",
|
|
"Clamp",
|
|
"RepeatX_ClampY",
|
|
"ClampX_RepeatY"
|
|
};
|
|
|
|
private readonly Func<GUIContent> _contentProvider;
|
|
private readonly int _wrapFlagBits;
|
|
private readonly int _flagIndex;
|
|
private readonly Func<bool> _isVisible;
|
|
|
|
public WrapModeItem(
|
|
ShaderGUIRootItem rootItem,
|
|
ShaderGUIItem parentItem,
|
|
int wrapFlagBits,
|
|
Func<GUIContent> contentProvider,
|
|
int flagIndex = 2,
|
|
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
|
{
|
|
_wrapFlagBits = wrapFlagBits;
|
|
_flagIndex = flagIndex;
|
|
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
|
_isVisible = isVisible;
|
|
GuiContent = _contentProvider();
|
|
CheckIsPropertyModified();
|
|
}
|
|
|
|
public override void OnGUI()
|
|
{
|
|
if (_isVisible != null && !_isVisible())
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetRect();
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.LabelField(LabelRect, GuiContent);
|
|
}
|
|
|
|
int mode = GetFirstMode();
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.showMixedValue = HasMixedValue();
|
|
EditorGUI.BeginChangeCheck();
|
|
using (new EditorGUIIndentLevelScope(0))
|
|
{
|
|
mode = EditorGUI.Popup(
|
|
ControlRect,
|
|
mode,
|
|
NBShaderInspectorLocalization.GetInspectorOptions("protocol.wrapMode", Options));
|
|
}
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
SetMode(mode);
|
|
CheckIsPropertyModified();
|
|
}
|
|
}
|
|
|
|
DrawResetButton();
|
|
}
|
|
|
|
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
|
{
|
|
PropertyIsDefaultValue = !HasMixedValue() && GetFirstMode() == 0;
|
|
HasModified = !PropertyIsDefaultValue;
|
|
ParentItem?.CheckIsPropertyModified(true);
|
|
}
|
|
|
|
public override void ExecuteReset(bool isCallByParent = false)
|
|
{
|
|
SetMode(0);
|
|
PropertyIsDefaultValue = true;
|
|
HasModified = false;
|
|
if (!isCallByParent)
|
|
{
|
|
ParentItem?.CheckIsPropertyModified(true);
|
|
}
|
|
}
|
|
|
|
private int GetFirstMode()
|
|
{
|
|
ShaderFlagsBase flags = RootItem.ShaderFlags.Count > 0 ? RootItem.ShaderFlags[0] : null;
|
|
if (flags == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int mode = flags.CheckFlagBits(_wrapFlagBits, index: _flagIndex) ? 1 : 0;
|
|
if (flags.CheckFlagBits(_wrapFlagBits << 16, index: _flagIndex))
|
|
{
|
|
mode += 2;
|
|
}
|
|
|
|
return mode;
|
|
}
|
|
|
|
private bool HasMixedValue()
|
|
{
|
|
int first = GetFirstMode();
|
|
for (int i = 1; i < RootItem.ShaderFlags.Count; i++)
|
|
{
|
|
ShaderFlagsBase flags = RootItem.ShaderFlags[i];
|
|
int mode = flags.CheckFlagBits(_wrapFlagBits, index: _flagIndex) ? 1 : 0;
|
|
if (flags.CheckFlagBits(_wrapFlagBits << 16, index: _flagIndex))
|
|
{
|
|
mode += 2;
|
|
}
|
|
|
|
if (mode != first)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void SetMode(int mode)
|
|
{
|
|
for (int i = 0; i < RootItem.ShaderFlags.Count; i++)
|
|
{
|
|
ShaderFlagsBase flags = RootItem.ShaderFlags[i];
|
|
SetFlag(flags, _wrapFlagBits, (mode & 1) != 0, _flagIndex);
|
|
SetFlag(flags, _wrapFlagBits << 16, (mode & 2) != 0, _flagIndex);
|
|
}
|
|
}
|
|
|
|
private static void SetFlag(ShaderFlagsBase flags, int bits, bool enabled, int index)
|
|
{
|
|
if (enabled)
|
|
{
|
|
flags.SetFlagBits(bits, index: index);
|
|
}
|
|
else
|
|
{
|
|
flags.ClearFlagBits(bits, index: index);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class PNoiseBlendModeItem : ShaderGUIItem
|
|
{
|
|
private static readonly string[] Options = { "不使用", "Multiply", "Min", "HardLight" };
|
|
private readonly Func<GUIContent> _contentProvider;
|
|
private readonly int _pNoiseBlendModeFlagPos;
|
|
private readonly Func<bool> _isVisible;
|
|
private readonly ShaderGUISliderItem _opacitySlider;
|
|
|
|
public PNoiseBlendModeItem(
|
|
NBShaderRootItem rootItem,
|
|
ShaderGUIItem parentItem,
|
|
int pNoiseBlendModeFlagPos,
|
|
string opacityPropertyName,
|
|
Func<GUIContent> contentProvider,
|
|
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
|
{
|
|
_pNoiseBlendModeFlagPos = pNoiseBlendModeFlagPos;
|
|
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
|
_isVisible = isVisible;
|
|
GuiContent = _contentProvider();
|
|
_opacitySlider = new ShaderGUISliderItem(rootItem, this)
|
|
{
|
|
PropertyName = opacityPropertyName,
|
|
GuiContent = NBShaderInspectorLocalization.MakeInspectorContent("protocol.pnoise.opacity", "Program Noise Blend Opacity"),
|
|
Min = 0f,
|
|
Max = 1f
|
|
};
|
|
_opacitySlider.InitTriggerByChild();
|
|
CheckIsPropertyModified();
|
|
}
|
|
|
|
public override void OnGUI()
|
|
{
|
|
if (_isVisible != null && !_isVisible())
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetRect();
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.LabelField(LabelRect, GuiContent);
|
|
}
|
|
|
|
NBShaderFlags.PNoiseBlendMode mode = GetFirstMode();
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.showMixedValue = HasMixedValue();
|
|
EditorGUI.BeginChangeCheck();
|
|
int index;
|
|
using (new EditorGUIIndentLevelScope(0))
|
|
{
|
|
index = EditorGUI.Popup(
|
|
ControlRect,
|
|
(int)mode,
|
|
NBShaderInspectorLocalization.GetInspectorOptions("protocol.pnoiseBlend", Options));
|
|
}
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
SetMode((NBShaderFlags.PNoiseBlendMode)index);
|
|
CheckIsPropertyModified();
|
|
}
|
|
}
|
|
|
|
DrawResetButton();
|
|
_opacitySlider.OnGUI();
|
|
}
|
|
|
|
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
|
{
|
|
PropertyIsDefaultValue = !HasMixedValue() && GetFirstMode() == NBShaderFlags.PNoiseBlendMode.NotUse;
|
|
HasModified = !PropertyIsDefaultValue;
|
|
foreach (ShaderGUIItem childItem in ChildrenItemList)
|
|
{
|
|
HasModified |= childItem.HasModified;
|
|
}
|
|
ParentItem?.CheckIsPropertyModified(true);
|
|
}
|
|
|
|
public override void ExecuteReset(bool isCallByParent = false)
|
|
{
|
|
SetMode(NBShaderFlags.PNoiseBlendMode.NotUse);
|
|
PropertyIsDefaultValue = true;
|
|
foreach (ShaderGUIItem childItem in ChildrenItemList)
|
|
{
|
|
childItem.ExecuteReset(true);
|
|
}
|
|
HasModified = false;
|
|
if (!isCallByParent)
|
|
{
|
|
ParentItem?.CheckIsPropertyModified(true);
|
|
}
|
|
}
|
|
|
|
private NBShaderFlags.PNoiseBlendMode GetFirstMode()
|
|
{
|
|
NBShaderFlags flags = GetFlags(0);
|
|
return flags == null ? NBShaderFlags.PNoiseBlendMode.NotUse : flags.GetPNoiseBlendMode(_pNoiseBlendModeFlagPos);
|
|
}
|
|
|
|
private bool HasMixedValue()
|
|
{
|
|
NBShaderFlags.PNoiseBlendMode first = GetFirstMode();
|
|
for (int i = 1; i < RootItem.ShaderFlags.Count; i++)
|
|
{
|
|
NBShaderFlags flags = GetFlags(i);
|
|
if (flags != null && flags.GetPNoiseBlendMode(_pNoiseBlendModeFlagPos) != first)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void SetMode(NBShaderFlags.PNoiseBlendMode mode)
|
|
{
|
|
for (int i = 0; i < RootItem.ShaderFlags.Count; i++)
|
|
{
|
|
GetFlags(i)?.SetPNoiseBlendMode(mode, _pNoiseBlendModeFlagPos);
|
|
}
|
|
}
|
|
|
|
private NBShaderFlags GetFlags(int index)
|
|
{
|
|
return index >= 0 &&
|
|
index < RootItem.ShaderFlags.Count &&
|
|
RootItem.ShaderFlags[index] is NBShaderFlags flags
|
|
? flags
|
|
: null;
|
|
}
|
|
}
|
|
|
|
public class UVModeSelectItem : ShaderGUIItem
|
|
{
|
|
private static readonly string[] UVModeNames =
|
|
{
|
|
"默认UV通道",
|
|
"特殊UV通道",
|
|
"极坐标|旋转",
|
|
"圆柱无缝",
|
|
"主贴图",
|
|
"屏幕UV",
|
|
"世界坐标",
|
|
"局部本地坐标",
|
|
"公共UV"
|
|
};
|
|
|
|
private static readonly string[] PosUVModeNames = { "xy平面", "xz平面", "yz平面" };
|
|
private static readonly string[] SpecialUVChannelNames = { "UV2_Texcoord1", "UV3_Texcoord2" };
|
|
|
|
private readonly Func<GUIContent> _contentProvider;
|
|
private readonly string _foldOutPropertyName;
|
|
private readonly int _uvModeBitPos;
|
|
private readonly int _uvModeFlagIndex;
|
|
private readonly string _texturePropertyName;
|
|
private readonly bool _forceEnable;
|
|
private readonly Func<bool> _isVisible;
|
|
private readonly ShaderGUIFoldOutHelper _foldOutHelper;
|
|
|
|
private readonly SpecialUVChannelModeItem _specialUVChannelItem;
|
|
private readonly PropertyToggleBlockItem _twirlBlock;
|
|
private readonly Vector2LineItem _twirlCenterItem;
|
|
private readonly ShaderGUIFloatItem _twirlStrengthItem;
|
|
private readonly PropertyToggleBlockItem _polarBlock;
|
|
private readonly Vector2LineItem _polarCenterItem;
|
|
private readonly VectorComponentItem _polarStrengthItem;
|
|
private readonly Vector3Item _cylinderRotateItem;
|
|
private readonly Vector3Item _cylinderOffsetItem;
|
|
private readonly ShaderGUIPopUpItem _worldSpaceItem;
|
|
private readonly ShaderGUIPopUpItem _objectSpaceItem;
|
|
|
|
public UVModeSelectItem(
|
|
NBShaderRootItem rootItem,
|
|
ShaderGUIItem parentItem,
|
|
string foldOutPropertyName,
|
|
int uvModeBitPos,
|
|
int uvModeFlagIndex,
|
|
Func<GUIContent> contentProvider,
|
|
string texturePropertyName = null,
|
|
bool forceEnable = false,
|
|
Func<bool> isVisible = null) : base(rootItem, parentItem)
|
|
{
|
|
_foldOutPropertyName = foldOutPropertyName;
|
|
_uvModeBitPos = uvModeBitPos;
|
|
_uvModeFlagIndex = uvModeFlagIndex;
|
|
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
|
_texturePropertyName = texturePropertyName;
|
|
_forceEnable = forceEnable;
|
|
_isVisible = isVisible;
|
|
_foldOutHelper = new ShaderGUIFoldOutHelper(rootItem, foldOutPropertyName);
|
|
GuiContent = _contentProvider();
|
|
|
|
_specialUVChannelItem = new SpecialUVChannelModeItem(rootItem, this);
|
|
_twirlBlock = new PropertyToggleBlockItem(
|
|
rootItem,
|
|
this,
|
|
"_GlobalTwirlFoldOut",
|
|
"_UTwirlEnabled",
|
|
() => NBShaderInspectorLocalization.MakeInspectorContent("protocol.uv.twirl", "Twirl"),
|
|
NBShaderFlags.FLAG_BIT_PARTICLE_UTWIRL_ON,
|
|
0);
|
|
_twirlCenterItem = new Vector2LineItem(rootItem, _twirlBlock, "_TWParameter", true, () => NBShaderInspectorLocalization.MakeInspectorContent("protocol.uv.twirlCenter", "Twirl Center"));
|
|
_twirlStrengthItem = new ShaderGUIFloatItem(rootItem, _twirlBlock)
|
|
{
|
|
PropertyName = "_TWStrength",
|
|
GuiContent = NBShaderInspectorLocalization.MakeInspectorContent("protocol.uv.twirlStrength", "Twirl Strength")
|
|
};
|
|
_twirlStrengthItem.InitTriggerByChild();
|
|
|
|
_polarBlock = new PropertyToggleBlockItem(
|
|
rootItem,
|
|
this,
|
|
"_GlobalPolarFoldOut",
|
|
"_PolarCoordinatesEnabled",
|
|
() => NBShaderInspectorLocalization.MakeInspectorContent("protocol.uv.polar", "Polar Coordinates"),
|
|
NBShaderFlags.FLAG_BIT_PARTICLE_POLARCOORDINATES_ON,
|
|
0);
|
|
_polarCenterItem = new Vector2LineItem(rootItem, _polarBlock, "_PCCenter", true, () => NBShaderInspectorLocalization.MakeInspectorContent("protocol.uv.polarCenter", "Polar Center"));
|
|
_polarStrengthItem = new VectorComponentItem(rootItem, _polarBlock, "_PCCenter", 2, () => NBShaderInspectorLocalization.MakeInspectorContent("protocol.uv.polarStrength", "Polar Strength"), true, 0f, 1f);
|
|
|
|
_cylinderRotateItem = new Vector3Item(rootItem, this, "_CylinderUVRotate", () => NBShaderInspectorLocalization.MakeInspectorContent("protocol.uv.cylinderRotate", "Cylinder Rotation"), _ => UpdateCylinderMatrix(rootItem));
|
|
_cylinderOffsetItem = new Vector3Item(rootItem, this, "_CylinderUVPosOffset", () => NBShaderInspectorLocalization.MakeInspectorContent("protocol.uv.cylinderOffset", "Cylinder Offset"), _ => UpdateCylinderMatrix(rootItem));
|
|
|
|
_worldSpaceItem = new ShaderGUIPopUpItem(rootItem, this)
|
|
{
|
|
PropertyName = "_WorldSpaceUVModeSelector",
|
|
GuiContent = NBShaderInspectorLocalization.MakeInspectorContent("protocol.uv.coordinatePlane", "Coordinate Plane"),
|
|
PopUpNames = NBShaderInspectorLocalization.GetInspectorOptions("protocol.uv.positionPlane", PosUVModeNames)
|
|
};
|
|
_worldSpaceItem.InitTriggerByChild();
|
|
|
|
_objectSpaceItem = new ShaderGUIPopUpItem(rootItem, this)
|
|
{
|
|
PropertyName = "_ObjectSpaceUVModeSelector",
|
|
GuiContent = NBShaderInspectorLocalization.MakeInspectorContent("protocol.uv.coordinatePlane", "Coordinate Plane"),
|
|
PopUpNames = NBShaderInspectorLocalization.GetInspectorOptions("protocol.uv.positionPlane", PosUVModeNames)
|
|
};
|
|
_objectSpaceItem.InitTriggerByChild();
|
|
|
|
CheckIsPropertyModified();
|
|
}
|
|
|
|
public override void OnGUI()
|
|
{
|
|
if (_isVisible != null && !_isVisible())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool controlDisabled = !_forceEnable && !HasTexture();
|
|
GetRect();
|
|
using (ParentControlDisabledScope(controlDisabled))
|
|
{
|
|
EditorGUI.LabelField(LabelRect, GuiContent);
|
|
}
|
|
|
|
NBShaderFlags.UVMode mode = GetFirstMode();
|
|
using (ParentControlDisabledScope(controlDisabled))
|
|
{
|
|
EditorGUI.showMixedValue = HasMixedValue();
|
|
EditorGUI.BeginChangeCheck();
|
|
int index;
|
|
using (new EditorGUIIndentLevelScope(0))
|
|
{
|
|
index = EditorGUI.Popup(
|
|
ControlRect,
|
|
(int)mode,
|
|
NBShaderInspectorLocalization.GetInspectorOptions("protocol.uv.mode", UVModeNames));
|
|
}
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
mode = (NBShaderFlags.UVMode)index;
|
|
SetMode(mode);
|
|
_foldOutHelper.SetOpen(NeedsFoldOut(mode));
|
|
CheckIsPropertyModified();
|
|
}
|
|
|
|
DrawResetButton();
|
|
}
|
|
|
|
bool needFoldOut = NeedsFoldOut(mode);
|
|
if (needFoldOut)
|
|
{
|
|
_foldOutHelper.DrawFoldOut(LabelRect);
|
|
}
|
|
|
|
if (needFoldOut && !HasMixedValue())
|
|
{
|
|
if (_foldOutHelper.BeginFadeGroup())
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
using (ParentControlDisabledScope(controlDisabled))
|
|
{
|
|
EditorGUI.LabelField(
|
|
ApplyGlobalRectCompensation(LayoutRect()),
|
|
NBShaderInspectorLocalization.GetInspectorText(
|
|
"protocol.uv.sharedMaterial.message",
|
|
"The following settings are shared in the material:"),
|
|
EditorStyles.boldLabel);
|
|
}
|
|
using (new InheritedControlDisabledScope(controlDisabled))
|
|
{
|
|
switch (mode)
|
|
{
|
|
case NBShaderFlags.UVMode.SpecialUVChannel:
|
|
_specialUVChannelItem.OnGUI();
|
|
break;
|
|
case NBShaderFlags.UVMode.PolarOrTwirl:
|
|
_twirlBlock.OnGUI();
|
|
_polarBlock.OnGUI();
|
|
break;
|
|
case NBShaderFlags.UVMode.Cylinder:
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.LabelField(
|
|
ApplyGlobalRectCompensation(LayoutRect()),
|
|
NBShaderInspectorLocalization.GetInspectorText(
|
|
"protocol.uv.cylinderWarning.message",
|
|
"Cylinder mode is expensive. Use it carefully."));
|
|
}
|
|
|
|
_cylinderRotateItem.OnGUI();
|
|
_cylinderOffsetItem.OnGUI();
|
|
UpdateCylinderMatrix(RootItem);
|
|
break;
|
|
case NBShaderFlags.UVMode.WorldPos:
|
|
_worldSpaceItem.OnGUI();
|
|
break;
|
|
case NBShaderFlags.UVMode.ObjectPos:
|
|
_objectSpaceItem.OnGUI();
|
|
break;
|
|
}
|
|
}
|
|
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
_foldOutHelper.EndFadedGroup();
|
|
}
|
|
}
|
|
|
|
public override void CheckIsPropertyModified(bool isCallByChild = false)
|
|
{
|
|
bool hasMixedValue = HasMixedValue();
|
|
NBShaderFlags.UVMode mode = GetFirstMode();
|
|
PropertyIsDefaultValue = !hasMixedValue && mode == NBShaderFlags.UVMode.DefaultUVChannel;
|
|
HasModified = !PropertyIsDefaultValue || (!hasMixedValue && HasActiveChildModified(mode));
|
|
ParentItem?.CheckIsPropertyModified(true);
|
|
}
|
|
|
|
public override void ExecuteReset(bool isCallByParent = false)
|
|
{
|
|
SetMode(NBShaderFlags.UVMode.DefaultUVChannel);
|
|
PropertyIsDefaultValue = true;
|
|
HasModified = false;
|
|
if (!isCallByParent)
|
|
{
|
|
ParentItem?.CheckIsPropertyModified(true);
|
|
}
|
|
}
|
|
|
|
private static bool NeedsFoldOut(NBShaderFlags.UVMode mode)
|
|
{
|
|
return mode != NBShaderFlags.UVMode.DefaultUVChannel &&
|
|
mode != NBShaderFlags.UVMode.CommonUV &&
|
|
mode != NBShaderFlags.UVMode.ScreenUV &&
|
|
mode != NBShaderFlags.UVMode.MainTex;
|
|
}
|
|
|
|
private bool HasActiveChildModified(NBShaderFlags.UVMode mode)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case NBShaderFlags.UVMode.SpecialUVChannel:
|
|
return _specialUVChannelItem?.HasModified == true;
|
|
case NBShaderFlags.UVMode.PolarOrTwirl:
|
|
return _twirlBlock?.HasModified == true || _polarBlock?.HasModified == true;
|
|
case NBShaderFlags.UVMode.Cylinder:
|
|
return _cylinderRotateItem?.HasModified == true || _cylinderOffsetItem?.HasModified == true;
|
|
case NBShaderFlags.UVMode.WorldPos:
|
|
return _worldSpaceItem?.HasModified == true;
|
|
case NBShaderFlags.UVMode.ObjectPos:
|
|
return _objectSpaceItem?.HasModified == true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private bool HasTexture()
|
|
{
|
|
if (string.IsNullOrEmpty(_texturePropertyName) ||
|
|
!RootItem.PropertyInfoDic.TryGetValue(_texturePropertyName, out ShaderPropertyInfo info))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return info.Property.hasMixedValue || info.Property.textureValue != null;
|
|
}
|
|
|
|
private NBShaderFlags.UVMode GetFirstMode()
|
|
{
|
|
NBShaderFlags flags = GetFlags(0);
|
|
return flags == null ? NBShaderFlags.UVMode.DefaultUVChannel : flags.GetUVMode(_uvModeBitPos, _uvModeFlagIndex);
|
|
}
|
|
|
|
private bool HasMixedValue()
|
|
{
|
|
NBShaderFlags.UVMode first = GetFirstMode();
|
|
for (int i = 1; i < RootItem.ShaderFlags.Count; i++)
|
|
{
|
|
NBShaderFlags flags = GetFlags(i);
|
|
if (flags != null && flags.GetUVMode(_uvModeBitPos, _uvModeFlagIndex) != first)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void SetMode(NBShaderFlags.UVMode mode)
|
|
{
|
|
for (int i = 0; i < RootItem.ShaderFlags.Count; i++)
|
|
{
|
|
GetFlags(i)?.SetUVMode(mode, _uvModeBitPos, _uvModeFlagIndex);
|
|
}
|
|
}
|
|
|
|
private NBShaderFlags GetFlags(int index)
|
|
{
|
|
return index >= 0 &&
|
|
index < RootItem.ShaderFlags.Count &&
|
|
RootItem.ShaderFlags[index] is NBShaderFlags flags
|
|
? flags
|
|
: null;
|
|
}
|
|
|
|
private static void UpdateCylinderMatrix(ShaderGUIRootItem rootItem)
|
|
{
|
|
if (!rootItem.PropertyInfoDic.TryGetValue("_CylinderUVRotate", out ShaderPropertyInfo rotateInfo) ||
|
|
!rootItem.PropertyInfoDic.TryGetValue("_CylinderUVPosOffset", out ShaderPropertyInfo offsetInfo))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Matrix4x4 cylinderMatrix = Matrix4x4.Translate(offsetInfo.Property.vectorValue) *
|
|
Matrix4x4.Rotate(Quaternion.Euler(rotateInfo.Property.vectorValue));
|
|
SetVector(rootItem, "_CylinderMatrix0", cylinderMatrix.GetRow(0));
|
|
SetVector(rootItem, "_CylinderMatrix1", cylinderMatrix.GetRow(1));
|
|
SetVector(rootItem, "_CylinderMatrix2", cylinderMatrix.GetRow(2));
|
|
SetVector(rootItem, "_CylinderMatrix3", cylinderMatrix.GetRow(3));
|
|
}
|
|
|
|
private static void SetVector(ShaderGUIRootItem rootItem, string propertyName, Vector4 value)
|
|
{
|
|
if (rootItem.PropertyInfoDic.TryGetValue(propertyName, out ShaderPropertyInfo info))
|
|
{
|
|
info.Property.vectorValue = value;
|
|
}
|
|
}
|
|
|
|
private class SpecialUVChannelModeItem : ShaderGUIPopUpItem
|
|
{
|
|
public SpecialUVChannelModeItem(ShaderGUIRootItem rootItem, ShaderGUIItem parentItem) : base(rootItem, parentItem)
|
|
{
|
|
PropertyName = "_SpecialUVChannelMode";
|
|
GuiContent = NBShaderInspectorLocalization.MakeInspectorContent("protocol.uv.specialChannel", "Special UV Channel");
|
|
PopUpNames = NBShaderInspectorLocalization.GetInspectorOptions("protocol.uv.specialChannel", SpecialUVChannelNames);
|
|
InitTriggerByChild();
|
|
}
|
|
|
|
public override void OnGUI()
|
|
{
|
|
base.OnGUI();
|
|
}
|
|
|
|
public override void OnEndChange()
|
|
{
|
|
base.OnEndChange();
|
|
bool useTexcoord1 = Mathf.RoundToInt(PropertyInfo.Property.floatValue) == 0;
|
|
for (int i = 0; i < RootItem.ShaderFlags.Count; i++)
|
|
{
|
|
ShaderFlagsBase flags = RootItem.ShaderFlags[i];
|
|
if (useTexcoord1)
|
|
{
|
|
flags.SetFlagBits(NBShaderFlags.FLAG_BIT_PARTICLE_1_USE_TEXCOORD1, index: 1);
|
|
flags.ClearFlagBits(NBShaderFlags.FLAG_BIT_PARTICLE_1_USE_TEXCOORD2, index: 1);
|
|
}
|
|
else
|
|
{
|
|
flags.ClearFlagBits(NBShaderFlags.FLAG_BIT_PARTICLE_1_USE_TEXCOORD1, index: 1);
|
|
flags.SetFlagBits(NBShaderFlags.FLAG_BIT_PARTICLE_1_USE_TEXCOORD2, index: 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public class KeywordListItem : ShaderGUIItem
|
|
{
|
|
private readonly Func<GUIContent> _contentProvider;
|
|
private Material _cachedMaterial;
|
|
private int _cachedKeywordVersion = -1;
|
|
private string[] _cachedKeywords = Array.Empty<string>();
|
|
|
|
public KeywordListItem(ShaderGUIRootItem rootItem, ShaderGUIItem parentItem, Func<GUIContent> contentProvider) : base(rootItem, parentItem)
|
|
{
|
|
_contentProvider = contentProvider ?? (() => GUIContent.none);
|
|
GuiContent = _contentProvider();
|
|
}
|
|
|
|
public override void OnGUI()
|
|
{
|
|
LayoutSpace();
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.LabelField(ApplyGlobalRectCompensation(LayoutRect()), GuiContent, EditorStyles.boldLabel);
|
|
}
|
|
|
|
if (RootItem.Mats == null || RootItem.Mats.Count == 0 || RootItem.Mats[0] == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string[] keywords = GetCachedKeywords(RootItem.Mats[0]);
|
|
if (keywords == null || keywords.Length == 0)
|
|
{
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.LabelField(
|
|
ApplyGlobalRectCompensation(LayoutRect()),
|
|
NBShaderInspectorLocalization.GetInspectorText("common.none", "None"));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < keywords.Length; i++)
|
|
{
|
|
using (ParentControlDisabledScope())
|
|
{
|
|
EditorGUI.LabelField(ApplyGlobalRectCompensation(LayoutRect()), keywords[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private string[] GetCachedKeywords(Material material)
|
|
{
|
|
int keywordVersion = RootItem is NBShaderRootItem nbRootItem && nbRootItem.SyncService != null
|
|
? nbRootItem.SyncService.KeywordVersion
|
|
: 0;
|
|
|
|
if (_cachedMaterial != material || _cachedKeywordVersion != keywordVersion)
|
|
{
|
|
_cachedMaterial = material;
|
|
_cachedKeywordVersion = keywordVersion;
|
|
_cachedKeywords = material != null ? material.shaderKeywords ?? Array.Empty<string>() : Array.Empty<string>();
|
|
}
|
|
|
|
return _cachedKeywords;
|
|
}
|
|
}
|
|
}
|