// Copyright (c) Jason Ma
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using Object = UnityEngine.Object;
namespace LWGUI
{
/// when LwguiEventType.Init: get all metadata from drawer
/// when LwguiEventType.Repaint: LWGUI decides how to draw each prop according to metadata
internal enum LwguiEventType
{
Init,
Repaint
}
internal enum SearchMode
{
All,
Modified
}
internal class LWGUI : ShaderGUI
{
public MaterialProperty[] props;
public MaterialEditor materialEditor;
public Material material;
public Dictionary searchResult;
public string searchingText = String.Empty;
public string lastSearchingText = String.Empty;
public SearchMode searchMode = SearchMode.All;
public SearchMode lastSearchMode = SearchMode.All;
public bool updateSearchMode = false;
public LwguiEventType lwguiEventType = LwguiEventType.Init;
public Shader shader;
private static bool _forceInit = false;
///
/// Called when switch to a new Material Window, each window has a LWGUI instance
///
public LWGUI() { }
public static void ForceInit() { _forceInit = true; }
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
{
this.props = props;
this.materialEditor = materialEditor;
this.material = materialEditor.target as Material;
this.shader = this.material.shader;
this.lwguiEventType = RevertableHelper.InitAndHasShaderModified(shader, material, props) || _forceInit
? LwguiEventType.Init
: LwguiEventType.Repaint;
// reset caches and metadata
if (lwguiEventType == LwguiEventType.Init)
{
MetaDataHelper.ClearCaches(shader);
searchResult = null;
lastSearchingText = searchingText = string.Empty;
lastSearchMode = searchMode = SearchMode.All;
updateSearchMode = false;
_forceInit = false;
MetaDataHelper.ReregisterAllPropertyMetaData(shader, material, props);
}
// draw with metadata
{
// Search Field
if (searchResult == null)
searchResult = MetaDataHelper.SearchProperties(shader, material, props, String.Empty, searchMode);
if (Helper.DrawSearchField(ref searchingText, ref searchMode, this) || updateSearchMode)
{
// change anything to expand all group
if ((string.IsNullOrEmpty(lastSearchingText) && lastSearchMode == SearchMode.All)) // last == init
GroupStateHelper.SetAllGroupFoldingAndCache(materialEditor.target, false);
// restore to the cached state
else if ((string.IsNullOrEmpty(searchingText) && searchMode == SearchMode.All)) // now == init
GroupStateHelper.RestoreCachedFoldingState(materialEditor.target);
searchResult = MetaDataHelper.SearchProperties(shader, material, props, searchingText, searchMode);
lastSearchingText = searchingText;
lastSearchMode = searchMode;
updateSearchMode = false;
}
// move fields left to make rect for Revert Button
materialEditor.SetDefaultGUIWidths();
EditorGUIUtility.fieldWidth += RevertableHelper.revertButtonWidth;
EditorGUIUtility.labelWidth -= RevertableHelper.revertButtonWidth;
RevertableHelper.fieldWidth = EditorGUIUtility.fieldWidth;
RevertableHelper.labelWidth = EditorGUIUtility.labelWidth;
// start drawing properties
foreach (var prop in props)
{
// force init when missing prop
if (!searchResult.ContainsKey(prop.name))
{
_forceInit = true;
return;
}
// ignored hidden prop
if ((prop.flags & MaterialProperty.PropFlags.HideInInspector) != 0 || !searchResult[prop.name])
continue;
var height = materialEditor.GetPropertyHeight(prop, MetaDataHelper.GetPropertyDisplayName(shader, prop));
// ignored when in Folding Group
if (height <= 0) continue;
Helper.DrawHelpbox(shader, prop);
// get rect
var rect = EditorGUILayout.GetControlRect(true, height, EditorStyles.layerMaskField);
var revertButtonRect = RevertableHelper.GetRevertButtonRect(prop, rect);
rect.xMax -= RevertableHelper.revertButtonWidth;
PresetHelper.DrawAddPropertyToPresetMenu(rect, shader, prop, props);
// fix some builtin types display misplaced
switch (prop.type)
{
case MaterialProperty.PropType.Texture:
case MaterialProperty.PropType.Range:
materialEditor.SetDefaultGUIWidths();
break;
default:
RevertableHelper.SetRevertableGUIWidths();
break;
}
RevertableHelper.DrawRevertableProperty(revertButtonRect, prop, materialEditor);
var label = new GUIContent(MetaDataHelper.GetPropertyDisplayName(shader, prop), MetaDataHelper.GetPropertyTooltip(shader, material, prop));
materialEditor.ShaderProperty(rect, prop, label);
}
}
materialEditor.SetDefaultGUIWidths();
EditorGUILayout.Space();
EditorGUILayout.Space();
#if UNITY_2019_4_OR_NEWER
if (SupportedRenderingFeatures.active.editableMaterialRenderQueue)
#endif
materialEditor.RenderQueueField();
materialEditor.EnableInstancingField();
materialEditor.DoubleSidedGIField();
EditorGUILayout.Space();
Helper.DrawLogo();
}
///
/// Find shader properties.
///
/// The name of the material property.
/// The array of available material properties.
/// If true then this method will throw an exception if a property with propertyName was not found.
///
/// The material property found, otherwise null.
///
public static MaterialProperty FindProp(string propertyName, MaterialProperty[] properties, bool propertyIsMandatory = false)
{
MaterialProperty outProperty= null;
if (properties == null)
{
Debug.LogWarning("Get other properties form Drawer is only support Unity 2019.2+!");
return null;
}
if (!string.IsNullOrEmpty(propertyName) && propertyName != "_")
outProperty = FindProperty(propertyName, properties, propertyIsMandatory);
else
return null;
if (outProperty == null)
ForceInit();
return outProperty;
}
}
internal class GroupStateHelper
{
// Used to Folding Group, key: group name, value: is folding
private static Dictionary> _groups = new Dictionary>();
private static Dictionary> _cachedGroups = new Dictionary>();
// Used to Conditional Display, key: keyword, value: is activated
private static Dictionary> _keywords = new Dictionary>();
// TODO: clear, reset to default, expand all, collapse all
private static void InitPoolPerMaterial(Object material)
{
if (!_groups.ContainsKey(material)) _groups.Add(material, new Dictionary());
if (!_cachedGroups.ContainsKey(material)) _cachedGroups.Add(material, new Dictionary());
if (!_keywords.ContainsKey(material)) _keywords.Add(material, new Dictionary());
}
public static bool ContainsGroup(Object material, string group)
{
InitPoolPerMaterial(material);
return _groups[material].ContainsKey(group);
}
public static void SetGroupFolding(Object material, string group, bool isFolding)
{
InitPoolPerMaterial(material);
_groups[material][group] = isFolding;
}
public static bool GetGroupFolding(Object material, string group)
{
InitPoolPerMaterial(material);
Debug.Assert(_groups[material].ContainsKey(group), "Unknown Group: " + group);
return _groups[material][group];
}
public static void SetAllGroupFoldingAndCache(Object material, bool isFolding)
{
InitPoolPerMaterial(material);
_cachedGroups[material] = new Dictionary(_groups[material]);
foreach (var group in _groups[material].Keys.ToArray())
{
_groups[material][group] = isFolding;
}
}
public static void RestoreCachedFoldingState(Object material)
{
InitPoolPerMaterial(material);
_groups[material] = new Dictionary(_cachedGroups[material]);
}
public static bool IsSubVisible(Object material, string group)
{
if (string.IsNullOrEmpty(group) || group == "_")
return true;
InitPoolPerMaterial(material);
// common sub
if (_groups[material].ContainsKey(group))
{
return !_groups[material][group];
}
// existing suffix, may be based on the enum conditions sub
else
{
foreach (var prefix in _groups[material].Keys)
{
// prefix = group name
if (group.Contains(prefix))
{
string suffix = group.Substring(prefix.Length, group.Length - prefix.Length).ToUpperInvariant();
return _keywords[material].Keys.Any((keyword =>
keyword.Contains(suffix) // fuzzy matching keyword and suffix
&& _keywords[material][keyword] // keyword is enabled
&& !_groups[material][prefix])); // group is not folding
}
}
return false;
}
}
public static void SetKeywordConditionalDisplay(Object material, string keyword, bool isDisplay)
{
if (string.IsNullOrEmpty(keyword) || keyword == "_") return;
InitPoolPerMaterial(material);
_keywords[material][keyword] = isDisplay;
}
}
///
/// Helpers for drawing Unreal Style Revertable Shader GUI
///
internal class RevertableHelper
{
public static readonly float revertButtonWidth = 15f;
public static float fieldWidth;
public static float labelWidth;
private static Dictionary >
_defaultProps = new Dictionary>();
private static Dictionary _lastShaderModifiedTime = new Dictionary();
private static Dictionary _lastShaders = new Dictionary();
private static bool _forceInit;
#region Init
private static void CheckProperty(Material material, MaterialProperty prop)
{
if (!(_defaultProps.ContainsKey(material) && _defaultProps[material].ContainsKey(prop.name)))
{
Debug.LogWarning("Uninitialized Shader:" + material.name + "or Prop:" + prop.name);
LWGUI.ForceInit();
}
}
public static void ForceInit() { _forceInit = true; }
///
/// Detect Shader changes to know when to initialize
///
public static bool InitAndHasShaderModified(Shader shader, Material material, MaterialProperty[] props)
{
var shaderPath = Application.dataPath.Substring(0, Application.dataPath.Length - 6) + AssetDatabase.GetAssetPath(shader);
Debug.Assert(File.Exists(shaderPath), "Unable to find Shader: " + shader.name + " in " + shaderPath + "!");
var currTime = (new FileInfo(shaderPath)).LastWriteTime;
// check for init
if (_forceInit
|| !_lastShaderModifiedTime.ContainsKey(shader)
|| _lastShaderModifiedTime[shader] != currTime
|| !_defaultProps.ContainsKey(material)
|| !_lastShaders.ContainsKey(material)
|| _lastShaders[material] != shader
)
{
if (_lastShaderModifiedTime.ContainsKey(shader) && _lastShaderModifiedTime[shader] != currTime)
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(shader));
_forceInit = false;
_lastShaders[material] = shader;
_lastShaderModifiedTime[shader] = currTime;
}
else
return false;
// Get and cache new props
var defaultMaterial = new Material(shader);
PresetHelper.ApplyPresetValue(props, defaultMaterial);
var newProps = MaterialEditor.GetMaterialProperties(new[] { defaultMaterial });
Debug.Assert(newProps.Length == props.Length);
_defaultProps[material] = new Dictionary();
foreach (var prop in newProps)
{
_defaultProps[material][prop.name] = prop;
}
return true;
}
#endregion
#region GUI Setting
public static Rect GetRevertButtonRect(MaterialProperty prop, Rect rect, bool isCallInDrawer = false)
{
// TODO: use Reflection
float defaultHeightWithoutDrawers = EditorGUIUtility.singleLineHeight;
return GetRevertButtonRect(defaultHeightWithoutDrawers, rect, isCallInDrawer);
}
public static Rect GetRevertButtonRect(float propHeight, Rect rect, bool isCallInDrawer = false)
{
if (isCallInDrawer) rect.xMax += revertButtonWidth;
var revertButtonRect = new Rect(rect.xMax - revertButtonWidth + 2f,
rect.yMax - propHeight * 0.5f - (revertButtonWidth - 3f) * 0.5f - 1f,
revertButtonWidth - 2f,
revertButtonWidth - 3f);
return revertButtonRect;
}
public static void SetRevertableGUIWidths()
{
EditorGUIUtility.fieldWidth = RevertableHelper.fieldWidth;
EditorGUIUtility.labelWidth = RevertableHelper.labelWidth;
}
#endregion
#region Property Handle
public static void SetPropertyToDefault(MaterialProperty defaultProp, MaterialProperty prop)
{
prop.vectorValue = defaultProp.vectorValue;
prop.colorValue = defaultProp.colorValue;
prop.floatValue = defaultProp.floatValue;
prop.textureValue = defaultProp.textureValue;
#if UNITY_2021_1_OR_NEWER
prop.intValue = defaultProp.intValue;
#endif
}
public static void SetPropertyToDefault(Material material, MaterialProperty prop)
{
CheckProperty(material, prop);
var defaultProp = _defaultProps[material][prop.name];
SetPropertyToDefault(defaultProp, prop);
}
public static MaterialProperty GetDefaultProperty(Material material, MaterialProperty prop)
{
CheckProperty(material, prop);
return _defaultProps[material][prop.name];
}
public static string GetPropertyDefaultValueText(Material material, MaterialProperty prop)
{
var defaultProp = GetDefaultProperty(material, prop);
string defaultText = String.Empty;
switch (defaultProp.type)
{
case MaterialProperty.PropType.Color:
defaultText += defaultProp.colorValue;
break;
case MaterialProperty.PropType.Float:
case MaterialProperty.PropType.Range:
defaultText += defaultProp.floatValue;
break;
#if UNITY_2021_1_OR_NEWER
case MaterialProperty.PropType.Int:
defaultText += defaultProp.intValue;
break;
#endif
case MaterialProperty.PropType.Texture:
defaultText += defaultProp.textureValue != null ? defaultProp.textureValue.name : "None";
break;
case MaterialProperty.PropType.Vector:
defaultText += defaultProp.vectorValue;
break;
}
return defaultText;
}
public static bool IsDefaultProperty(Material material, MaterialProperty prop)
{
CheckProperty(material, prop);
return Helper.PropertyValueEquals(prop, _defaultProps[material][prop.name]);
}
#endregion
#region Draw revert button
public static bool DrawRevertableProperty(Rect position, MaterialProperty prop, MaterialEditor materialEditor)
{
var material = materialEditor.target as Material;
CheckProperty(material, prop);
var defaultProp = _defaultProps[material][prop.name];
Rect rect = position;
if (Helper.PropertyValueEquals(prop, defaultProp) && !prop.hasMixedValue)
return false;
if (DrawRevertButton(rect))
{
AddPropertyShouldRevert(prop.targets, prop.name);
SetPropertyToDefault(defaultProp, prop);
// refresh keywords
MaterialEditor.ApplyMaterialPropertyDrawers(materialEditor.targets);
return true;
}
return false;
}
private static readonly Texture _icon = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath("e7bc1130858d984488bca32b8512ca96"));
public static bool DrawRevertButton(Rect rect)
{
if (_icon == null) Debug.LogError("RevertIcon.png + meta is missing!");
GUI.DrawTexture(rect, _icon);
var e = Event.current;
if (e.type == UnityEngine.EventType.MouseDown && rect.Contains(e.mousePosition))
{
e.Use();
return true;
}
return false;
}
#endregion
#region Call drawers to do revert and refresh keywords
private static Dictionary> _shouldRevertPropsPool;
public static void AddPropertyShouldRevert(Object[] materials, string propName)
{
if (_shouldRevertPropsPool == null)
_shouldRevertPropsPool = new Dictionary>();
foreach (var material in materials)
{
if (_shouldRevertPropsPool.ContainsKey(material))
{
if (!_shouldRevertPropsPool[material].Contains(propName))
_shouldRevertPropsPool[material].Add(propName);
}
else
{
_shouldRevertPropsPool.Add(material, new List { propName });
}
}
}
public static void RemovePropertyShouldRevert(Object[] materials, string propName)
{
if (_shouldRevertPropsPool == null) return;
foreach (var material in materials)
{
if (_shouldRevertPropsPool.ContainsKey(material))
{
if (_shouldRevertPropsPool[material].Contains(propName))
_shouldRevertPropsPool[material].Remove(propName);
}
}
}
public static bool IsPropertyShouldRevert(Object material, string propName)
{
if (_shouldRevertPropsPool == null) return false;
if (_shouldRevertPropsPool.ContainsKey(material))
{
return _shouldRevertPropsPool[material].Contains(propName);
}
else
{
return false;
}
}
#endregion
}
///
/// Misc Function
///
internal class Helper
{
#region Engine Misc
public static void ObsoleteWarning(string obsoleteStr, string newStr)
{
Debug.LogWarning("'" + obsoleteStr + "' is Obsolete! Please use '" + newStr + "'!");
}
public static bool PropertyValueEquals(MaterialProperty prop1, MaterialProperty prop2)
{
if (prop1.textureValue == prop2.textureValue
&& prop1.vectorValue == prop2.vectorValue
&& prop1.colorValue == prop2.colorValue
&& prop1.floatValue == prop2.floatValue
#if UNITY_2021_1_OR_NEWER
&& prop1.intValue == prop2.intValue
#endif
)
return true;
else
return false;
}
public static string GetKeyWord(string keyWord, string propName)
{
string k;
if (string.IsNullOrEmpty(keyWord) || keyWord == "__")
{
k = propName.ToUpperInvariant() + "_ON";
}
else
{
k = keyWord.ToUpperInvariant();
}
return k;
}
public static void SetShaderKeyWord(Object[] materials, string keyWord, bool isEnable)
{
if (string.IsNullOrEmpty(keyWord) || string.IsNullOrEmpty(keyWord)) return;
foreach (Material m in materials)
{
// delete "_" keywords
if (keyWord == "_")
{
if (m.IsKeywordEnabled(keyWord))
{
m.DisableKeyword(keyWord);
}
continue;
}
if (m.IsKeywordEnabled(keyWord))
{
if (!isEnable) m.DisableKeyword(keyWord);
}
else
{
if (isEnable) m.EnableKeyword(keyWord);
}
}
}
public static void SetShaderKeyWord(Object[] materials, string[] keyWords, int index)
{
Debug.Assert(keyWords.Length >= 1 && index < keyWords.Length && index >= 0, "KeyWords Length: " + keyWords.Length + " or Index: " + index + " Error! ");
for (int i = 0; i < keyWords.Length; i++)
{
SetShaderKeyWord(materials, keyWords[i], index == i);
}
}
///
/// make Drawer can get all current Material props by customShaderGUI
/// Unity 2019.2+
///
public static LWGUI GetLWGUI(MaterialEditor editor)
{
var customShaderGUI = ReflectionHelper.GetCustomShaderGUI(editor);
if (customShaderGUI != null && customShaderGUI is LWGUI)
{
LWGUI gui = customShaderGUI as LWGUI;
return gui;
}
else
{
Debug.LogWarning("Please add \"CustomEditor \"LWGUI.LWGUI\"\" to the end of your shader!");
return null;
}
}
public static void AdaptiveFieldWidth(string str, GUIContent content, float extraWidth = 0)
{
AdaptiveFieldWidth(new GUIStyle(str), content, extraWidth);
}
public static void AdaptiveFieldWidth(GUIStyle style, GUIContent content, float extraWidth = 0)
{
var extraTextWidth = Mathf.Max(0, style.CalcSize(content).x + extraWidth - EditorGUIUtility.fieldWidth);
EditorGUIUtility.labelWidth -= extraTextWidth;
EditorGUIUtility.fieldWidth += extraTextWidth;
}
#endregion
#region Math
public static float PowPreserveSign(float f, float p)
{
float num = Mathf.Pow(Mathf.Abs(f), p);
if ((double)f < 0.0)
return -num;
return num;
}
#endregion
#region Draw GUI for Drawer
// TODO: use Reflection
// copy and edit of https://github.com/GucioDevs/SimpleMinMaxSlider/blob/master/Assets/SimpleMinMaxSlider/Scripts/Editor/MinMaxSliderDrawer.cs
public static Rect[] SplitRect(Rect rectToSplit, int n)
{
Rect[] rects = new Rect[n];
for (int i = 0; i < n; i++)
{
rects[i] = new Rect(rectToSplit.position.x + (i * rectToSplit.width / n), rectToSplit.position.y, rectToSplit.width / n, rectToSplit.height);
}
int padding = (int)rects[0].width - 50; // use 50, enough to show 0.xx (2 digits)
int space = 5;
rects[0].width -= padding + space;
rects[2].width -= padding + space;
rects[1].x -= padding;
rects[1].width += padding * 2;
rects[2].x += padding + space;
return rects;
}
public static bool Foldout(Rect position, ref bool isFolding, bool toggleValue, bool hasToggle, GUIContent label)
{
var style = new GUIStyle("ShurikenModuleTitle");
style.border = new RectOffset(15, 7, 4, 4);
style.fixedHeight = 30;
// Text
style.font = new GUIStyle(EditorStyles.boldLabel).font;
style.fontSize = (int)(style.fontSize * 1.5f);
style.contentOffset = new Vector2(30f, -2f);
var rect = position; //GUILayoutUtility.GetRect(position.width, 24f, style);
GUI.backgroundColor = isFolding ? Color.white : new Color(0.85f, 0.85f, 0.85f);
GUI.Box(rect, label, style);
GUI.backgroundColor = Color.white;
var toggleRect = new Rect(rect.x + 8f, rect.y + 7f, 13f, 13f);
if (hasToggle)
{
EditorGUI.BeginChangeCheck();
GUI.Toggle(toggleRect, EditorGUI.showMixedValue ? false : toggleValue, String.Empty, new GUIStyle(EditorGUI.showMixedValue ? "ToggleMixed" : "Toggle"));
if (EditorGUI.EndChangeCheck())
toggleValue = !toggleValue;
}
var e = Event.current;
if (e.type == UnityEngine.EventType.MouseDown && rect.Contains(e.mousePosition))
{
isFolding = !isFolding;
e.Use();
}
return toggleValue;
}
// TODO: use Reflection
public static void PowerSlider(MaterialProperty prop, float power, Rect position, GUIContent label)
{
int controlId = GUIUtility.GetControlID("EditorSliderKnob".GetHashCode(), FocusType.Passive, position);
float left = prop.rangeLimits.x;
float right = prop.rangeLimits.y;
float start = left;
float end = right;
float value = prop.floatValue;
float originValue = prop.floatValue;
if ((double)power != 1.0)
{
start = Helper.PowPreserveSign(start, 1f / power);
end = Helper.PowPreserveSign(end, 1f / power);
value = Helper.PowPreserveSign(value, 1f / power);
}
EditorGUI.BeginChangeCheck();
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 0;
var rectAfterLabel = EditorGUI.PrefixLabel(position, label);
Rect sliderRect = MaterialEditor.GetFlexibleRectBetweenLabelAndField(position);
sliderRect.xMin += 2;
if (sliderRect.width >= 50f)
// TODO: Slider Focus
value = GUI.Slider(sliderRect, value, 0.0f, start, end, GUI.skin.horizontalSlider, !EditorGUI.showMixedValue ? GUI.skin.horizontalSliderThumb : (GUIStyle)"SliderMixed", true,
controlId);
if ((double)power != 1.0)
value = Helper.PowPreserveSign(value, power);
position.xMin = Mathf.Max(rectAfterLabel.xMin, sliderRect.xMax - 10f);
var floatRect = position;
value = EditorGUI.FloatField(floatRect, value);
if (value != originValue)
prop.floatValue = Mathf.Clamp(value, Mathf.Min(left, right), Mathf.Max(left, right));
EditorGUIUtility.labelWidth = labelWidth;
}
#endregion
#region Draw GUI for Material
public static readonly float helpboxSingleLineHeight = 12.5f;
public static void DrawHelpbox(Shader shader, MaterialProperty prop)
{
int lineCount;
var helpboxStr = MetaDataHelper.GetPropertyHelpbox(shader, prop, out lineCount);
if (!string.IsNullOrEmpty(helpboxStr))
{
var content = new GUIContent(helpboxStr, EditorGUIUtility.IconContent("console.infoicon").image as Texture2D);
var style = EditorStyles.helpBox;
var dpiScale = EditorGUIUtility.pixelsPerPoint;
int fontSize = 12;
style.fontSize = fontSize;
var helpboxRect = EditorGUILayout.GetControlRect(true, style.CalcHeight(content, EditorGUIUtility.currentViewWidth));
if (MetaDataHelper.IsSubProperty(shader, prop))
{
EditorGUI.indentLevel++;
helpboxRect = EditorGUI.IndentedRect(helpboxRect);
EditorGUI.indentLevel--;
}
helpboxRect.xMax -= RevertableHelper.revertButtonWidth;
GUI.Label(helpboxRect, content, style);
// EditorGUI.HelpBox(helpboxRect, helpboxStr, MessageType.Info);
}
}
private static Texture _logo = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath("26b9d845eb7b1a747bf04dc84e5bcc2c"));
public static void DrawLogo()
{
var logoRect = EditorGUILayout.GetControlRect(false, _logo.height);
var w = logoRect.width;
logoRect.xMin += w * 0.5f - _logo.width * 0.5f;
logoRect.xMax -= w * 0.5f - _logo.width * 0.5f;
if (EditorGUIUtility.currentViewWidth >= logoRect.width)
{
var c = GUI.color;
GUI.color = new Color(c.r, c.g, c.b, 0.4f);
if (logoRect.Contains(Event.current.mousePosition))
{
GUI.color = new Color(c.r, c.g, c.b, 0.8f);
if (Event.current.type == UnityEngine.EventType.MouseDown)
Application.OpenURL("https://github.com/JasonMa0012/LWGUI");
}
GUI.DrawTexture(logoRect, _logo);
GUI.color = c;
GUI.Label(logoRect, new GUIContent(String.Empty, "LWGUI (Light Weight Shader GUI)\n\n"
+ "A Lightweight, Flexible, Powerful Unity Shader GUI system.\n\n"
+ "Copyright (c) Jason Ma"));
}
}
private static readonly int s_TextFieldHash = "EditorTextField".GetHashCode();
private static readonly GUIContent[] _searchModeMenus = new[]
{
new GUIContent(SearchMode.All.ToString()),
new GUIContent(SearchMode.Modified.ToString())
};
/// is has changed?
public static bool DrawSearchField(ref string searchingText, ref SearchMode searchMode, LWGUI lwgui)
{
var toolbarSeachTextFieldPopup = new GUIStyle("ToolbarSeachTextFieldPopup");
bool isHasChanged = false;
EditorGUI.BeginChangeCheck();
var rect = EditorGUILayout.GetControlRect();
var revertButtonRect = RevertableHelper.GetRevertButtonRect(EditorGUIUtility.singleLineHeight, rect);
rect.xMax -= RevertableHelper.revertButtonWidth;
// Get internal TextField ControlID
int controlId = GUIUtility.GetControlID(s_TextFieldHash, FocusType.Keyboard, rect) + 1;
// searching mode
Rect modeRect = new Rect(rect);
modeRect.width = 20f;
if (Event.current.type == UnityEngine.EventType.MouseDown && modeRect.Contains(Event.current.mousePosition))
{
EditorUtility.DisplayCustomMenu(rect, _searchModeMenus, (int)searchMode,
(data, options, selected) =>
{
if (lwgui.searchMode != (SearchMode)selected)
{
lwgui.searchMode = (SearchMode)selected;
lwgui.updateSearchMode = true;
}
}, null);
Event.current.Use();
}
// TODO: use Reflection -> controlId
searchingText = EditorGUI.TextField(rect, String.Empty, searchingText, toolbarSeachTextFieldPopup);
if (EditorGUI.EndChangeCheck())
isHasChanged = true;
// revert button
if ((!string.IsNullOrEmpty(searchingText) || searchMode != SearchMode.All) && RevertableHelper.DrawRevertButton(revertButtonRect))
{
searchingText = string.Empty;
searchMode = SearchMode.All;
isHasChanged = true;
GUIUtility.keyboardControl = 0;
}
// display search mode
if (GUIUtility.keyboardControl != controlId && string.IsNullOrEmpty(searchingText) && Event.current.type == UnityEngine.EventType.Repaint)
{
using (new EditorGUI.DisabledScope(true))
{
Rect position1 = toolbarSeachTextFieldPopup.padding.Remove(new Rect(rect.x, rect.y, rect.width,
toolbarSeachTextFieldPopup.fixedHeight > 0.0 ? toolbarSeachTextFieldPopup.fixedHeight : rect.height));
int fontSize = EditorStyles.label.fontSize;
EditorStyles.label.fontSize = toolbarSeachTextFieldPopup.fontSize;
EditorStyles.label.Draw(position1, new GUIContent(searchMode.ToString()), false, false, false, false);
EditorStyles.label.fontSize = fontSize;
}
}
return isHasChanged;
}
#endregion
}
internal class RampHelper
{
[Serializable]
public class GradientObject : ScriptableObject
{
[SerializeField] public Gradient gradient = new Gradient();
}
private static readonly GUIContent _iconAdd = new GUIContent(EditorGUIUtility.IconContent("d_Toolbar Plus").image, "Add"),
_iconEdit = new GUIContent(EditorGUIUtility.IconContent("editicon.sml").image, "Edit"),
_iconDiscard = new GUIContent(EditorGUIUtility.IconContent("d_TreeEditor.Refresh").image, "Discard"),
_iconSave = new GUIContent(EditorGUIUtility.IconContent("SaveActive").image, "Save");
private static readonly GUIStyle _styleEdit = new GUIStyle("button");
public static bool RampEditor(
MaterialProperty prop,
Rect buttonRect,
SerializedProperty gradientProperty,
bool isDirty,
string defaultFileName,
int defaultWidth,
int defaultHeight,
out Texture newTexture,
out bool doSave,
out bool doDiscard)
{
newTexture = null;
var hasChange = false;
var shouldCreate = false;
var singleButtonWidth = buttonRect.width * 0.25f;
var editRect = new Rect(buttonRect.x + singleButtonWidth * 0, buttonRect.y, singleButtonWidth, buttonRect.height);
var saveRect = new Rect(buttonRect.x + singleButtonWidth * 1, buttonRect.y, singleButtonWidth, buttonRect.height);
var addRect = new Rect(buttonRect.x + singleButtonWidth * 2, buttonRect.y, singleButtonWidth, buttonRect.height);
var discardRect = new Rect(buttonRect.x + singleButtonWidth * 3, buttonRect.y, singleButtonWidth, buttonRect.height);
// if the current edited texture is null, create new one
var currEvent = Event.current;
if (prop.textureValue == null && currEvent.type == UnityEngine.EventType.MouseDown && editRect.Contains(currEvent.mousePosition))
{
shouldCreate = true;
currEvent.Use();
}
// Gradient Editor
var gradientPropertyRect = new Rect(editRect.x + 2, editRect.y + 2, editRect.width - 2, editRect.height - 2);
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(gradientPropertyRect, gradientProperty, GUIContent.none);
if (EditorGUI.EndChangeCheck()) hasChange = true;
// Edit Icon override
if (currEvent.type == UnityEngine.EventType.Repaint)
{
var isHover = editRect.Contains(currEvent.mousePosition);
_styleEdit.Draw(editRect, _iconEdit, isHover, false, false, false);
}
// Create Ramp Texture
if (GUI.Button(addRect, _iconAdd) || shouldCreate)
{
var path = EditorUtility.SaveFilePanel("Create New Ramp Texture", lastSavePath, defaultFileName, "png");
if (path.Contains(projectPath))
{
lastSavePath = Path.GetDirectoryName(path);
//Create texture and save PNG
var saveUnityPath = path.Replace(projectPath, String.Empty);
CreateAndSaveNewGradientTexture(defaultWidth, defaultHeight, saveUnityPath);
//Load created texture
newTexture = AssetDatabase.LoadAssetAtPath(saveUnityPath);
}
else if (!string.IsNullOrEmpty(path))
{
Debug.LogError("Invalid Path: "
+ path
+ "\n"
+ "Please make sure you chosen Unity Project Relative Path");
}
}
var color = GUI.color;
if (isDirty) GUI.color = Color.yellow;
doSave = GUI.Button(saveRect, _iconSave);
GUI.color = color;
doDiscard = GUI.Button(discardRect, _iconDiscard);
return hasChange;
}
public static readonly string projectPath = Application.dataPath.Substring(0, Application.dataPath.Length - 6);
public static string lastSavePath
{
get { return EditorPrefs.GetString("LWGUI_GradientSavePath_" + Application.version, Application.dataPath); }
set
{
if (value.Contains(projectPath))
EditorPrefs.SetString("LWGUI_GradientSavePath_" + Application.version, value);
}
}
public static Gradient GetGradientFromTexture(Texture texture, out bool isDirty, bool doReimporte = false)
{
isDirty = false;
if (texture == null) return null;
var assetImporter = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(texture));
if (assetImporter != null && assetImporter.userData.Contains("LWGUI"))
{
GradientObject savedGradientObject, editingGradientObject;
isDirty = DecodeGradientFromJSON(assetImporter.userData, out savedGradientObject, out editingGradientObject);
return doReimporte ? savedGradientObject.gradient : editingGradientObject.gradient;
}
else
{
Debug.LogError("Can not find texture: "
+ texture.name
+ " or it's userData on disk! \n"
+ "If you are moving or copying the Ramp Map, make sure your .meta file is not lost!");
return null;
}
}
public static void SetGradientToTexture(Texture texture, GradientObject gradientObject, bool doSaveToDisk = false)
{
if (texture == null || gradientObject.gradient == null) return;
var texture2D = (Texture2D)texture;
// Save to texture
var path = AssetDatabase.GetAssetPath(texture);
var pixels = GetPixelsFromGradient(gradientObject.gradient, texture.width, texture.height);
texture2D.SetPixels(pixels);
texture2D.Apply(true, false);
// Save gradient JSON to userData
var assetImporter = AssetImporter.GetAtPath(path);
GradientObject savedGradientObject, editingGradientObject;
DecodeGradientFromJSON(assetImporter.userData, out savedGradientObject, out editingGradientObject);
assetImporter.userData = EncodeGradientToJSON(doSaveToDisk ? gradientObject : savedGradientObject, gradientObject);
// Save texture to disk
if (doSaveToDisk)
{
var systemPath = projectPath + path;
File.WriteAllBytes(systemPath, texture2D.EncodeToPNG());
assetImporter.SaveAndReimport();
}
}
private static string EncodeGradientToJSON(GradientObject savedGradientObject, GradientObject editingGradientObject)
{
string savedJSON = " ", editingJSON = " ";
if (savedGradientObject != null)
savedJSON = EditorJsonUtility.ToJson(savedGradientObject);
if (editingGradientObject != null)
editingJSON = EditorJsonUtility.ToJson(editingGradientObject);
return savedJSON + "#" + editingJSON;
}
private static bool DecodeGradientFromJSON(string json, out GradientObject savedGradientObject, out GradientObject editingGradientObject)
{
var subJSONs = json.Split('#');
savedGradientObject = ScriptableObject.CreateInstance();
if (subJSONs[0] != " ")
EditorJsonUtility.FromJsonOverwrite(subJSONs[0], savedGradientObject);
editingGradientObject = ScriptableObject.CreateInstance();
if (subJSONs[1] != " ")
EditorJsonUtility.FromJsonOverwrite(subJSONs[1], editingGradientObject);
return subJSONs[0] != subJSONs[1];
}
public static bool CreateAndSaveNewGradientTexture(int width, int height, string unityPath)
{
var gradientObject = ScriptableObject.CreateInstance();
gradientObject.gradient = new Gradient();
gradientObject.gradient.colorKeys = new[] { new GradientColorKey(Color.gray, 0.0f), new GradientColorKey(Color.white, 1.0f) };
gradientObject.gradient.alphaKeys = new[] { new GradientAlphaKey(0f, 0f), new GradientAlphaKey(1f, 1f) };
var ramp = CreateGradientTexture(gradientObject.gradient, width, height);
var png = ramp.EncodeToPNG();
Object.DestroyImmediate(ramp);
var systemPath = projectPath + unityPath;
File.WriteAllBytes(systemPath, png);
AssetDatabase.ImportAsset(unityPath);
var textureImporter = AssetImporter.GetAtPath(unityPath) as TextureImporter;
textureImporter.wrapMode = TextureWrapMode.Clamp;
textureImporter.isReadable = true;
textureImporter.textureCompression = TextureImporterCompression.Uncompressed;
textureImporter.alphaSource = TextureImporterAlphaSource.FromInput;
//Gradient data embedded in userData
textureImporter.userData = EncodeGradientToJSON(gradientObject, gradientObject);
textureImporter.SaveAndReimport();
return true;
}
private static Texture2D CreateGradientTexture(Gradient gradient, int width, int height)
{
var ramp = new Texture2D(width, height, TextureFormat.RGBA32, true, true);
var colors = GetPixelsFromGradient(gradient, width, height);
ramp.SetPixels(colors);
ramp.Apply(true);
return ramp;
}
private static Color[] GetPixelsFromGradient(Gradient gradient, int width, int height)
{
var pixels = new Color[width * height];
for (var x = 0; x < width; x++)
{
var delta = x / (float)width;
if (delta < 0) delta = 0;
if (delta > 1) delta = 1;
var col = gradient.Evaluate(delta);
for (int i = 0; i < height; i++)
{
pixels[x + i * width] = col;
}
}
return pixels;
}
}
///
/// Provide Metadata for drawing
///
internal class MetaDataHelper
{
#region Meta Data Container
private static Dictionary >> _mainSubDic = new Dictionary>>();
private static Dictionary > _mainGroupNameDic = new Dictionary>();
private static Dictionary > _propParentDic = new Dictionary>();
private static Dictionary >> _extraPropDic = new Dictionary>>();
private static Dictionary >> _tooltipDic = new Dictionary>>();
private static Dictionary >> _defaultDic = new Dictionary>>();
private static Dictionary >> _helpboxDic = new Dictionary>>();
private static Dictionary >> _presetDic = new Dictionary>>();
public static void ClearCaches(Shader shader)
{
if (_mainSubDic.ContainsKey(shader)) _mainSubDic[shader].Clear();
if (_mainGroupNameDic.ContainsKey(shader)) _mainGroupNameDic[shader].Clear();
if (_propParentDic.ContainsKey(shader)) _propParentDic[shader].Clear();
if (_extraPropDic.ContainsKey(shader)) _extraPropDic[shader].Clear();
if (_tooltipDic.ContainsKey(shader)) _tooltipDic[shader].Clear();
if (_defaultDic.ContainsKey(shader)) _defaultDic[shader].Clear();
if (_helpboxDic.ContainsKey(shader)) _helpboxDic[shader].Clear();
if (_presetDic.ContainsKey(shader)) _presetDic[shader].Clear();
}
#endregion
#region Main - Sub
public static void RegisterMainProp(Shader shader, MaterialProperty prop, string group)
{
if (_mainSubDic.ContainsKey(shader))
{
if (!_mainSubDic[shader].ContainsKey(prop.name))
{
_mainSubDic[shader].Add(prop.name, new List());
}
}
else
{
_mainSubDic.Add(shader, new Dictionary>());
_mainSubDic[shader].Add(prop.name, new List());
}
if (_mainGroupNameDic.ContainsKey(shader))
{
if (!_mainGroupNameDic[shader].ContainsKey(group))
{
_mainGroupNameDic[shader].Add(group, prop.name);
}
}
else
{
_mainGroupNameDic.Add(shader, new Dictionary());
_mainGroupNameDic[shader].Add(group, prop.name);
}
}
public static void RegisterSubProp(Shader shader, MaterialProperty prop, string group, MaterialProperty[] extraProps = null)
{
if (!string.IsNullOrEmpty(group) && group != "_")
{
// add to _mainSubDic
if (_mainGroupNameDic.ContainsKey(shader))
{
var groupName = _mainGroupNameDic[shader].Keys.First((s => group.Contains(s)));
if (!string.IsNullOrEmpty(groupName))
{
var mainPropName = _mainGroupNameDic[shader][groupName];
if (_mainSubDic[shader].ContainsKey(mainPropName))
{
if (!_mainSubDic[shader][mainPropName].Contains(prop.name))
_mainSubDic[shader][mainPropName].Add(prop.name);
}
else
Debug.LogError("Unregistered Main Property:" + mainPropName);
// add to _propParentDic
if (!_propParentDic.ContainsKey(shader))
_propParentDic.Add(shader, new Dictionary());
if (!_propParentDic[shader].ContainsKey(prop.name))
_propParentDic[shader].Add(prop.name, groupName);
}
else
Debug.LogError("Unregistered Main Group Name: " + group);
}
else
Debug.LogError("Unregistered Shader: " + shader.name);
}
// add to _extraPropDic
if (extraProps != null)
{
if (!_extraPropDic.ContainsKey(shader))
_extraPropDic.Add(shader, new Dictionary>());
if (!_extraPropDic[shader].ContainsKey(prop.name))
_extraPropDic[shader].Add(prop.name, new List());
foreach (var extraProp in extraProps)
{
if (extraProp != null)
{
if (!_extraPropDic[shader][prop.name].Contains(extraProp.name))
_extraPropDic[shader][prop.name].Add(extraProp.name);
}
}
}
}
public static bool IsSubProperty(Shader shader, MaterialProperty prop)
{
var isSubProp = false;
if (_propParentDic.ContainsKey(shader) && _propParentDic[shader].ContainsKey(prop.name))
isSubProp = true;
return isSubProp;
}
#endregion
private static void RegisterProperty(Shader shader, MaterialProperty prop, T value, Dictionary>> dst)
{
if (!dst.ContainsKey(shader))
dst.Add(shader, new Dictionary>());
if (!dst[shader].ContainsKey(prop.name))
dst[shader].Add(prop.name, new List());
dst[shader][prop.name].Add(value);
}
private static string GetPropertyString(Shader shader, MaterialProperty prop, Dictionary>> src, out int lineCount)
{
var str = string.Empty;
lineCount = 0;
if (src.ContainsKey(shader) && src[shader].ContainsKey(prop.name))
{
for (int i = 0; i < src[shader][prop.name].Count; i++)
{
if (i > 0) str += "\n";
str += src[shader][prop.name][i];
lineCount++;
}
}
return str;
}
public static void ReregisterAllPropertyMetaData(Shader shader, Material material, MaterialProperty[] props)
{
foreach (var prop in props)
{
List decoratorDrawers;
var drawer = ReflectionHelper.GetPropertyDrawer(shader, prop, out decoratorDrawers);
if (decoratorDrawers != null && decoratorDrawers.Count > 0)
{
foreach (var decoratorDrawer in decoratorDrawers)
{
if (decoratorDrawer is IBaseDrawer)
(decoratorDrawer as IBaseDrawer).InitMetaData(shader, material, prop, props);
}
}
if (drawer != null)
{
if (drawer is IBaseDrawer)
(drawer as IBaseDrawer).InitMetaData(shader, material, prop, props);
}
DisplayNameToTooltipAndHelpbox(shader, prop);
}
}
#region Tooltip
public static void RegisterPropertyDefaultValueText(Shader shader, MaterialProperty prop, string text)
{
RegisterProperty(shader, prop, text, _defaultDic);
}
public static void RegisterPropertyTooltip(Shader shader, MaterialProperty prop, string text)
{
RegisterProperty(shader, prop, text, _tooltipDic);
}
private static string GetPropertyDefaultValueText(Shader shader, Material material, MaterialProperty prop)
{
int lineCount;
var defaultText = GetPropertyString(shader, prop, _defaultDic, out lineCount);
if (string.IsNullOrEmpty(defaultText))
// TODO: use Reflection - handle builtin Toggle / Enum
defaultText = RevertableHelper.GetPropertyDefaultValueText(material, prop);
return defaultText;
}
public static string GetPropertyTooltip(Shader shader, Material material, MaterialProperty prop)
{
int lineCount;
var str = GetPropertyString(shader, prop, _tooltipDic, out lineCount);
if (!string.IsNullOrEmpty(str))
str += "\n\n";
str += "Name: " + prop.name + "\n";
str += "Default: " + GetPropertyDefaultValueText(shader, material, prop);
return str;
}
#endregion
#region Helpbox
public static void RegisterPropertyHelpbox(Shader shader, MaterialProperty prop, string text)
{
RegisterProperty(shader, prop, text, _helpboxDic);
}
public static string GetPropertyHelpbox(Shader shader, MaterialProperty prop, out int lineCount)
{
return GetPropertyString(shader, prop, _helpboxDic, out lineCount);
}
#endregion
#region Display Name
private static readonly string _tooltipString = "#";
private static readonly string _helpboxString = "%";
public static string GetPropertyDisplayName(Shader shader, MaterialProperty prop)
{
var tooltipIndex = prop.displayName.IndexOf(_tooltipString, StringComparison.Ordinal);
var helpboxIndex = prop.displayName.IndexOf(_helpboxString, StringComparison.Ordinal);
var minIndex = tooltipIndex == -1 ? helpboxIndex : tooltipIndex;
if (tooltipIndex != -1 && helpboxIndex != -1)
minIndex = Mathf.Min(minIndex, helpboxIndex);
if (minIndex == -1)
return prop.displayName;
else if (minIndex == 0)
return string.Empty;
else
return prop.displayName.Substring(0, minIndex);
}
public static void DisplayNameToTooltipAndHelpbox(Shader shader, MaterialProperty prop)
{
var tooltips = prop.displayName.Split(new String[] { _tooltipString }, StringSplitOptions.None);
if (tooltips.Length > 1)
{
for (int i = 1; i <= tooltips.Length - 1; i++)
{
var str = tooltips[i];
var helpboxIndex = tooltips[i].IndexOf(_helpboxString, StringComparison.Ordinal);
if (helpboxIndex == 0)
str = "\n";
else if (helpboxIndex > 0)
str = tooltips[i].Substring(0, helpboxIndex);
RegisterPropertyTooltip(shader, prop, str);
}
}
var helpboxes = prop.displayName.Split(new String[] { _helpboxString }, StringSplitOptions.None);
if (helpboxes.Length > 1)
{
for (int i = 1; i <= helpboxes.Length - 1; i++)
{
var str = helpboxes[i];
var tooltipIndex = helpboxes[i].IndexOf(_tooltipString, StringComparison.Ordinal);
if (tooltipIndex == 0)
str = "\n";
else if (tooltipIndex > 0)
str = tooltips[i].Substring(0, tooltipIndex);
RegisterPropertyHelpbox(shader, prop, str);
}
}
}
#endregion
#region Preset
public static void RegisterPropertyPreset(Shader shader, MaterialProperty prop, ShaderPropertyPreset shaderPropertyPreset)
{
RegisterProperty(shader, prop, shaderPropertyPreset, _presetDic);
}
public static Dictionary GetAllPropertyPreset(Shader shader, MaterialProperty[] props)
{
var result = new Dictionary();
var presetProps = props.Where((property =>
_presetDic.ContainsKey(shader) && _presetDic[shader].ContainsKey(property.name)));
foreach (var presetProp in presetProps)
{
result.Add(presetProp, _presetDic[shader][presetProp.name][0]);
}
return result;
}
#endregion
public static Dictionary SearchProperties(Shader shader, Material material, MaterialProperty[] props, string searchingText, SearchMode searchMode)
{
var result = new Dictionary();
var isDefaultProps = new Dictionary();
if (searchMode == SearchMode.Modified)
{
foreach (var prop in props)
{
isDefaultProps.Add(prop.name, RevertableHelper.IsDefaultProperty(material, prop));
}
}
if (string.IsNullOrEmpty(searchingText) && searchMode == SearchMode.All)
{
foreach (var prop in props)
{
result.Add(prop.name, true);
}
}
else
{
foreach (var prop in props)
{
bool contains = true;
// filter props
if (searchMode == SearchMode.Modified)
{
contains = !isDefaultProps[prop.name];
if (!contains && _extraPropDic.ContainsKey(shader) && _extraPropDic[shader].ContainsKey(prop.name))
{
foreach (var extraPropName in _extraPropDic[shader][prop.name])
{
contains = !isDefaultProps[extraPropName];
if (contains) break;
}
}
}
// whole word match search
var displayName = GetPropertyDisplayName(shader, prop).ToLower();
var name = prop.name.ToLower();
searchingText = searchingText.ToLower();
var keywords = searchingText.Split(' ', ',', ';', '|', '*', '&'); // Some possible separators
foreach (var keyword in keywords)
{
var isMatch = false;
isMatch |= displayName.Contains(keyword);
isMatch |= name.Contains(keyword);
contains &= isMatch;
}
result.Add(prop.name, contains);
}
// when a SubProp display, MainProp will also display
if (_mainSubDic.ContainsKey(shader))
{
foreach (var prop in props)
{
if (_mainSubDic[shader].ContainsKey(prop.name))
{
// foreach sub prop in main
foreach (var subPropName in _mainSubDic[shader][prop.name])
{
if (result.ContainsKey(subPropName))
{
if (result[subPropName])
{
result[prop.name] = true;
break;
}
}
}
}
}
}
}
return result;
}
}
internal class ReflectionHelper
{
// get private members in UnityEditor class
public static Assembly UnityEditor_Assembly = Assembly.GetAssembly(typeof(Editor));
public static Type MaterialPropertyHandler_Type = UnityEditor_Assembly.GetType("UnityEditor.MaterialPropertyHandler");
public static MethodInfo MaterialPropertyHandler_GetHandler_Method = MaterialPropertyHandler_Type.GetMethod("GetHandler", BindingFlags.Static | BindingFlags.NonPublic);
public static PropertyInfo MaterialPropertyHandler_PropertyDrawer_Property = MaterialPropertyHandler_Type.GetProperty("propertyDrawer");
public static FieldInfo MaterialPropertyHandler_DecoratorDrawers_Field = MaterialPropertyHandler_Type.GetField("m_DecoratorDrawers", BindingFlags.NonPublic | BindingFlags.Instance);
public static MaterialPropertyDrawer GetPropertyDrawer(Shader shader, MaterialProperty prop, out List decoratorDrawers)
{
decoratorDrawers = new List();
var handler = MaterialPropertyHandler_GetHandler_Method.Invoke(null, new System.Object[] { shader, prop.name });
if (handler != null && handler.GetType() == MaterialPropertyHandler_Type)
{
decoratorDrawers = MaterialPropertyHandler_DecoratorDrawers_Field.GetValue(handler) as List;
return MaterialPropertyHandler_PropertyDrawer_Property.GetValue(handler, null) as MaterialPropertyDrawer;
}
return null;
}
public static MaterialPropertyDrawer GetPropertyDrawer(Shader shader, MaterialProperty prop)
{
List decoratorDrawers;
return GetPropertyDrawer(shader, prop, out decoratorDrawers);
}
#if !UNITY_2019_2_OR_NEWER
public static Type MaterialEditor_Type = UnityEditor_Assembly.GetType("UnityEditor.MaterialEditor");
public static FieldInfo MaterialEditor_CustomShaderGUI_Field = MaterialEditor_Type.GetField("m_CustomShaderGUI", BindingFlags.NonPublic | BindingFlags.Instance);
#endif
public static ShaderGUI GetCustomShaderGUI(MaterialEditor editor)
{
#if !UNITY_2019_2_OR_NEWER
return MaterialEditor_CustomShaderGUI_Field.GetValue(editor) as ShaderGUI;
#else
return editor.customShaderGUI;
#endif
}
// UnityEditor.MaterialEnumDrawer(string enumName)
private static System.Type[] _types;
public static System.Type[] GetAllTypes()
{
if (_types == null)
{
_types = ((IEnumerable)AppDomain.CurrentDomain.GetAssemblies())
.SelectMany((Func>)
(assembly =>
{
if (assembly == null)
return (IEnumerable)(new System.Type[0]);
try
{
return (IEnumerable)assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
return (IEnumerable)(new System.Type[0]);
}
})).ToArray();
}
return _types;
}
}
internal class PresetHelper
{
private static Dictionary _loadedPresets = new Dictionary();
private static bool _isInitComplete;
public static bool IsInitComplete { get { return _isInitComplete; } }
public static void Init()
{
if (!_isInitComplete)
{
ForceInit();
}
}
public static void ForceInit()
{
_loadedPresets.Clear();
_isInitComplete = false;
var GUIDs = AssetDatabase.FindAssets("t:" + typeof(ShaderPropertyPreset));
foreach (var GUID in GUIDs)
{
var preset = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(GUID));
AddPreset(preset);
}
_isInitComplete = true;
}
public static void AddPreset(ShaderPropertyPreset preset)
{
if (!preset) return;
if (!_loadedPresets.ContainsKey(preset.name))
{
_loadedPresets.Add(preset.name, preset);
// Debug.Log(preset.name);
}
}
public static ShaderPropertyPreset GetPresetFile(string presetFileName)
{
if (!_loadedPresets.ContainsKey(presetFileName) || !_loadedPresets[presetFileName])
ForceInit();
if (!_loadedPresets.ContainsKey(presetFileName) || !_loadedPresets[presetFileName])
{
Debug.LogError("Invalid ShaderPropertyPreset: ‘" + presetFileName + "’ !");
return null;
}
return _loadedPresets[presetFileName];
}
public static void ApplyPresetValue(MaterialProperty[] props, Material material)
{
Init();
List _drawers = new List();
List _properties = new List();
// Update Value
foreach (var prop in props)
{
var drawer = ReflectionHelper.GetPropertyDrawer(material.shader, prop);
if (drawer != null)
{
_drawers.Add(drawer);
_properties.Add(prop);
if (drawer is PresetDrawer)
{
var preset = GetPresetFile((drawer as PresetDrawer).presetFileName);
if (preset)
preset.Apply(material, (int)prop.floatValue);
}
}
}
// Update Keyword
for (int i = 0; i < _properties.Count; i++)
{
_drawers[i].Apply(_properties[i]);
}
}
private enum PresetOperation
{
Add = 0,
Update = 1,
Remove = 2
}
public static void DrawAddPropertyToPresetMenu(Rect rect, Shader shader, MaterialProperty prop, MaterialProperty[] props)
{
if (Event.current.type == EventType.ContextClick && rect.Contains(Event.current.mousePosition))
{
// Get Menu Content
var propDisplayName = MetaDataHelper.GetPropertyDisplayName(shader, prop);
var propPresetDic = MetaDataHelper.GetAllPropertyPreset(shader, props);
if (propPresetDic.Count == 0) return;
// Create Menus
var operations = new Dictionary();
var propValues = new Dictionary();
var presets = new Dictionary();
GUIContent[] menus = propPresetDic.SelectMany(((keyValuePair, i) =>
{
if (prop.name == keyValuePair.Key.name) return new List();
var preset = keyValuePair.Value.GetPreset(keyValuePair.Key);
var propertyValue = preset.propertyValues.Find((value => value.propertyName == prop.name));
if (propertyValue == null)
{
var content = new GUIContent("Add '" + propDisplayName + "' to '" + preset.presetName + "'");
operations.Add(content, PresetOperation.Add);
propValues.Add(content, propertyValue);
presets.Add(content, preset);
return new List() { content };
}
else
{
var contentUpdate = new GUIContent("Update '" + propDisplayName + "' in '" + preset.presetName + "'");
operations.Add(contentUpdate, PresetOperation.Update);
propValues.Add(contentUpdate, propertyValue);
presets.Add(contentUpdate, preset);
var contentRemove = new GUIContent("Remove '" + propDisplayName + "' from '" + preset.presetName + "'");
operations.Add(contentRemove, PresetOperation.Remove);
propValues.Add(contentRemove, propertyValue);
presets.Add(contentRemove, preset);
return new List() { contentUpdate, contentRemove };
}
})).ToArray();
EditorUtility.DisplayCustomMenu(new Rect(Event.current.mousePosition.x, Event.current.mousePosition.y, 0, 0),
// Call Click Event
menus, -1, (data, options, selected) =>
{
var menu = menus[selected];
var operation = operations[menu];
var preset = presets[menu];
var propertyValue = propValues[menu];
if (propertyValue == null)
{
propertyValue = new ShaderPropertyPreset.PropertyValue();
propertyValue.CopyFromMaterialProperty(prop);
preset.propertyValues.Add(propertyValue);
}
else if (operation == PresetOperation.Update)
{
propertyValue.CopyFromMaterialProperty(prop);
}
else
{
preset.propertyValues.Remove(propertyValue);
}
RevertableHelper.ForceInit();
}, null);
}
}
}
} //namespace LWGUI