// 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