592 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			592 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|   | //-------------------------------------------------------------------------------------------------------------------------------- | ||
|  | // Cartoon FX | ||
|  | // (c) 2012-2020 Jean Moreno | ||
|  | //-------------------------------------------------------------------------------------------------------------------------------- | ||
|  | 
 | ||
|  | using UnityEngine; | ||
|  | using UnityEditor; | ||
|  | using System.Collections.Generic; | ||
|  | using System.IO; | ||
|  | using System.Text.RegularExpressions; | ||
|  | 
 | ||
|  | // Custom material inspector for Stylized FX shaders | ||
|  | // - organize UI using comments in the shader code | ||
|  | // - more flexibility than the material property drawers | ||
|  | // version 2 (dec 2017) | ||
|  | 
 | ||
|  | namespace CartoonFX | ||
|  | { | ||
|  | 	public class MaterialInspector : ShaderGUI | ||
|  | 	{ | ||
|  | 		//Set by PropertyDrawers to defined if the next properties should be visible | ||
|  | 		static private Stack<bool> ShowStack = new Stack<bool>(); | ||
|  | 
 | ||
|  | 		static public bool ShowNextProperty { get; private set; } | ||
|  | 		static public void PushShowProperty(bool value) | ||
|  | 		{ | ||
|  | 			ShowStack.Push(ShowNextProperty); | ||
|  | 			ShowNextProperty &= value; | ||
|  | 		} | ||
|  | 		static public void PopShowProperty() | ||
|  | 		{ | ||
|  | 			ShowNextProperty = ShowStack.Pop(); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		//-------------------------------------------------------------------------------------------------- | ||
|  | 
 | ||
|  | 		const string kGuiCommandPrefix = "//#"; | ||
|  | 		const string kGC_IfKeyword = "IF_KEYWORD"; | ||
|  | 		const string kGC_IfProperty = "IF_PROPERTY"; | ||
|  | 		const string kGC_EndIf = "END_IF"; | ||
|  | 		const string kGC_HelpBox = "HELP_BOX"; | ||
|  | 		const string kGC_Label = "LABEL"; | ||
|  | 
 | ||
|  | 		Dictionary<int, List<GUICommand>> guiCommands = new Dictionary<int, List<GUICommand>>(); | ||
|  | 
 | ||
|  | 		bool initialized = false; | ||
|  | 		AssetImporter shaderImporter; | ||
|  | 		ulong lastTimestamp; | ||
|  | 		void Initialize(MaterialEditor editor, bool force) | ||
|  | 		{ | ||
|  | 			if((!initialized || force) && editor != null) | ||
|  | 			{ | ||
|  | 				initialized = true; | ||
|  | 
 | ||
|  | 				guiCommands.Clear(); | ||
|  | 
 | ||
|  | 				//Find the shader and parse the source to find special comments that will organize the GUI | ||
|  | 				//It's hackish, but at least it allows any character to be used (unlike material property drawers/decorators) and can be used along with property drawers | ||
|  | 
 | ||
|  | 				var materials = new List<Material>(); | ||
|  | 				foreach(var o in editor.targets) | ||
|  | 				{ | ||
|  | 					var m = o as Material; | ||
|  | 					if(m != null) | ||
|  | 						materials.Add(m); | ||
|  | 				} | ||
|  | 				if(materials.Count > 0 && materials[0].shader != null) | ||
|  | 				{ | ||
|  | 					var path = AssetDatabase.GetAssetPath(materials[0].shader); | ||
|  | 					//get asset importer | ||
|  | 					shaderImporter = AssetImporter.GetAtPath(path); | ||
|  | 					if(shaderImporter != null) | ||
|  | 					{ | ||
|  | 						lastTimestamp = shaderImporter.assetTimeStamp; | ||
|  | 					} | ||
|  | 					//remove 'Assets' and replace with OS path | ||
|  | 					path = Application.dataPath + path.Substring(6); | ||
|  | 					//convert to cross-platform path | ||
|  | 					path = path.Replace('/', Path.DirectorySeparatorChar); | ||
|  | 					//open file for reading | ||
|  | 					var lines = File.ReadAllLines(path); | ||
|  | 
 | ||
|  | 					bool insideProperties = false; | ||
|  | 					//regex pattern to find properties, as they need to be counted so that | ||
|  | 					//special commands can be inserted at the right position when enumerating them | ||
|  | 					var regex = new Regex(@"[a-zA-Z0-9_]+\s*\([^\)]*\)"); | ||
|  | 					int propertyCount = 0; | ||
|  | 					bool insideCommentBlock = false; | ||
|  | 					foreach(var l in lines) | ||
|  | 					{ | ||
|  | 						var line = l.TrimStart(); | ||
|  | 
 | ||
|  | 						if(insideProperties) | ||
|  | 						{ | ||
|  | 							bool isComment = line.StartsWith("//"); | ||
|  | 
 | ||
|  | 							if(line.Contains("/*")) | ||
|  | 								insideCommentBlock = true; | ||
|  | 							if(line.Contains("*/")) | ||
|  | 								insideCommentBlock = false; | ||
|  | 
 | ||
|  | 							//finished properties block? | ||
|  | 							if(line.StartsWith("}")) | ||
|  | 								break; | ||
|  | 
 | ||
|  | 							//comment | ||
|  | 							if(line.StartsWith(kGuiCommandPrefix)) | ||
|  | 							{ | ||
|  | 								string command = line.Substring(kGuiCommandPrefix.Length).TrimStart(); | ||
|  | 								//space | ||
|  | 								if(string.IsNullOrEmpty(command)) | ||
|  | 									AddGUICommand(propertyCount, new GC_Space()); | ||
|  | 								//separator | ||
|  | 								else if(command.StartsWith("---")) | ||
|  | 									AddGUICommand(propertyCount, new GC_Separator()); | ||
|  | 								//separator | ||
|  | 								else if(command.StartsWith("===")) | ||
|  | 									AddGUICommand(propertyCount, new GC_SeparatorDouble()); | ||
|  | 								//if keyword | ||
|  | 								else if(command.StartsWith(kGC_IfKeyword)) | ||
|  | 								{ | ||
|  | 									var expr = command.Substring(command.LastIndexOf(kGC_IfKeyword) + kGC_IfKeyword.Length + 1); | ||
|  | 									AddGUICommand(propertyCount, new GC_IfKeyword() { expression = expr, materials = materials.ToArray() }); | ||
|  | 								} | ||
|  | 								//if property | ||
|  | 								else if(command.StartsWith(kGC_IfProperty)) | ||
|  | 								{ | ||
|  | 									var expr = command.Substring(command.LastIndexOf(kGC_IfProperty) + kGC_IfProperty.Length + 1); | ||
|  | 									AddGUICommand(propertyCount, new GC_IfProperty() { expression = expr, materials = materials.ToArray() }); | ||
|  | 								} | ||
|  | 								//end if | ||
|  | 								else if(command.StartsWith(kGC_EndIf)) | ||
|  | 								{ | ||
|  | 									AddGUICommand(propertyCount, new GC_EndIf()); | ||
|  | 								} | ||
|  | 								//help box | ||
|  | 								else if(command.StartsWith(kGC_HelpBox)) | ||
|  | 								{ | ||
|  | 									var messageType = MessageType.Error; | ||
|  | 									var message = "Invalid format for HELP_BOX:\n" + command; | ||
|  | 									var cmd = command.Substring(command.LastIndexOf(kGC_HelpBox) + kGC_HelpBox.Length + 1).Split(new string[] { "::" }, System.StringSplitOptions.RemoveEmptyEntries); | ||
|  | 									if(cmd.Length == 1) | ||
|  | 									{ | ||
|  | 										message = cmd[0]; | ||
|  | 										messageType = MessageType.None; | ||
|  | 									} | ||
|  | 									else if(cmd.Length == 2) | ||
|  | 									{ | ||
|  | 										try | ||
|  | 										{ | ||
|  | 											var msgType = (MessageType)System.Enum.Parse(typeof(MessageType), cmd[0], true); | ||
|  | 											message = cmd[1].Replace("  ", "\n"); | ||
|  | 											messageType = msgType; | ||
|  | 										} | ||
|  | 										catch { } | ||
|  | 									} | ||
|  | 									 | ||
|  | 									AddGUICommand(propertyCount, new GC_HelpBox() | ||
|  | 									{ | ||
|  | 										message = message, | ||
|  | 										messageType = messageType | ||
|  | 									}); | ||
|  | 								} | ||
|  | 								//label | ||
|  | 								else if(command.StartsWith(kGC_Label)) | ||
|  | 								{ | ||
|  | 									var label = command.Substring(command.LastIndexOf(kGC_Label) + kGC_Label.Length + 1); | ||
|  | 									AddGUICommand(propertyCount, new GC_Label() { label = label }); | ||
|  | 								} | ||
|  | 								//header: plain text after command | ||
|  | 								else | ||
|  | 								{ | ||
|  | 									AddGUICommand(propertyCount, new GC_Header() { label = command }); | ||
|  | 								} | ||
|  | 							} | ||
|  | 							else | ||
|  | 							//property | ||
|  | 							{ | ||
|  | 								if(regex.IsMatch(line) && !insideCommentBlock && !isComment) | ||
|  | 									propertyCount++; | ||
|  | 							} | ||
|  | 						} | ||
|  | 
 | ||
|  | 						//start properties block? | ||
|  | 						if(line.StartsWith("Properties")) | ||
|  | 						{ | ||
|  | 							insideProperties = true; | ||
|  | 						} | ||
|  | 					} | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		void AddGUICommand(int propertyIndex, GUICommand command) | ||
|  | 		{ | ||
|  | 			if(!guiCommands.ContainsKey(propertyIndex)) | ||
|  | 				guiCommands.Add(propertyIndex, new List<GUICommand>()); | ||
|  | 
 | ||
|  | 			guiCommands[propertyIndex].Add(command); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader) | ||
|  | 		{ | ||
|  | 			initialized = false; | ||
|  | 			base.AssignNewShaderToMaterial(material, oldShader, newShader); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) | ||
|  | 		{ | ||
|  | 			//init: | ||
|  | 			//- read metadata in properties comment to generate ui layout | ||
|  | 			//- force update if timestamp doesn't match last (= file externally updated) | ||
|  | 			bool force = (shaderImporter != null && shaderImporter.assetTimeStamp != lastTimestamp); | ||
|  | 			Initialize(materialEditor, force); | ||
|  | 
 | ||
|  | 			var shader = (materialEditor.target as Material).shader; | ||
|  | 			materialEditor.SetDefaultGUIWidths(); | ||
|  | 
 | ||
|  | 			//show all properties by default | ||
|  | 			ShowNextProperty = true; | ||
|  | 			ShowStack.Clear(); | ||
|  | 
 | ||
|  | 			for(int i = 0; i < properties.Length; i++) | ||
|  | 			{ | ||
|  | 				if(guiCommands.ContainsKey(i)) | ||
|  | 				{ | ||
|  | 					for(int j = 0; j < guiCommands[i].Count; j++) | ||
|  | 					{ | ||
|  | 						guiCommands[i][j].OnGUI(); | ||
|  | 					} | ||
|  | 				} | ||
|  | 
 | ||
|  | 				//Use custom properties to enable/disable groups based on keywords | ||
|  | 				if(ShowNextProperty) | ||
|  | 				{ | ||
|  | 					if((properties[i].flags & (MaterialProperty.PropFlags.HideInInspector | MaterialProperty.PropFlags.PerRendererData)) == MaterialProperty.PropFlags.None) | ||
|  | 					{ | ||
|  | 						DisplayProperty(properties[i], materialEditor); | ||
|  | 					} | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			//make sure to show gui commands that are after properties | ||
|  | 			int index = properties.Length; | ||
|  | 			if(guiCommands.ContainsKey(index)) | ||
|  | 			{ | ||
|  | 				for(int j = 0; j < guiCommands[index].Count; j++) | ||
|  | 				{ | ||
|  | 					guiCommands[index][j].OnGUI(); | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			//Special fields | ||
|  | 			Styles.MaterialDrawSeparatorDouble(); | ||
|  | 			materialEditor.RenderQueueField(); | ||
|  | 			materialEditor.EnableInstancingField(); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		virtual protected void DisplayProperty(MaterialProperty property, MaterialEditor materialEditor) | ||
|  | 		{ | ||
|  | 			float propertyHeight = materialEditor.GetPropertyHeight(property, property.displayName); | ||
|  | 			Rect controlRect = EditorGUILayout.GetControlRect(true, propertyHeight, EditorStyles.layerMaskField, new GUILayoutOption[0]); | ||
|  | 			materialEditor.ShaderProperty(controlRect, property, property.displayName); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Same as Toggle drawer, but doesn't set any keyword | ||
|  | 	// This will avoid adding unnecessary shader keyword to the project | ||
|  | 	internal class MaterialToggleNoKeywordDrawer : MaterialPropertyDrawer | ||
|  | 	{ | ||
|  | 		private static bool IsPropertyTypeSuitable(MaterialProperty prop) | ||
|  | 		{ | ||
|  | 			return prop.type == MaterialProperty.PropType.Float || prop.type == MaterialProperty.PropType.Range; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) | ||
|  | 		{ | ||
|  | 			float height; | ||
|  | 			if (!MaterialToggleNoKeywordDrawer.IsPropertyTypeSuitable(prop)) | ||
|  | 			{ | ||
|  | 				height = 40f; | ||
|  | 			} | ||
|  | 			else | ||
|  | 			{ | ||
|  | 				height = base.GetPropertyHeight(prop, label, editor); | ||
|  | 			} | ||
|  | 			return height; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) | ||
|  | 		{ | ||
|  | 			if (!MaterialToggleNoKeywordDrawer.IsPropertyTypeSuitable(prop)) | ||
|  | 			{ | ||
|  | 				EditorGUI.HelpBox(position, "Toggle used on a non-float property: " + prop.name, MessageType.Warning); | ||
|  | 			} | ||
|  | 			else | ||
|  | 			{ | ||
|  | 				EditorGUI.BeginChangeCheck(); | ||
|  | 				bool flag = Mathf.Abs(prop.floatValue) > 0.001f; | ||
|  | 				EditorGUI.showMixedValue = prop.hasMixedValue; | ||
|  | 				flag = EditorGUI.Toggle(position, label, flag); | ||
|  | 				EditorGUI.showMixedValue = false; | ||
|  | 				if (EditorGUI.EndChangeCheck()) | ||
|  | 				{ | ||
|  | 					prop.floatValue = ((!flag) ? 0f : 1f); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Same as KeywordEnum drawer, but uses the keyword supplied as is rather than adding a prefix to them | ||
|  | 	internal class MaterialKeywordEnumNoPrefixDrawer : MaterialPropertyDrawer | ||
|  | 	{ | ||
|  | 		private readonly GUIContent[] labels; | ||
|  | 		private readonly string[] keywords; | ||
|  | 
 | ||
|  | 		public MaterialKeywordEnumNoPrefixDrawer(string lbl1, string kw1) : this(new[] { lbl1 }, new[] { kw1 }) { } | ||
|  | 		public MaterialKeywordEnumNoPrefixDrawer(string lbl1, string kw1, string lbl2, string kw2) : this(new[] { lbl1, lbl2 }, new[] { kw1, kw2 }) { } | ||
|  | 		public MaterialKeywordEnumNoPrefixDrawer(string lbl1, string kw1, string lbl2, string kw2, string lbl3, string kw3) : this(new[] { lbl1, lbl2, lbl3 }, new[] { kw1, kw2, kw3 }) { } | ||
|  | 		public MaterialKeywordEnumNoPrefixDrawer(string lbl1, string kw1, string lbl2, string kw2, string lbl3, string kw3, string lbl4, string kw4) : this(new[] { lbl1, lbl2, lbl3, lbl4 }, new[] { kw1, kw2, kw3, kw4 }) { } | ||
|  | 		public MaterialKeywordEnumNoPrefixDrawer(string lbl1, string kw1, string lbl2, string kw2, string lbl3, string kw3, string lbl4, string kw4, string lbl5, string kw5) : this(new[] { lbl1, lbl2, lbl3, lbl4, lbl5 }, new[] { kw1, kw2, kw3, kw4, kw5 }) { } | ||
|  | 		public MaterialKeywordEnumNoPrefixDrawer(string lbl1, string kw1, string lbl2, string kw2, string lbl3, string kw3, string lbl4, string kw4, string lbl5, string kw5, string lbl6, string kw6) : this(new[] { lbl1, lbl2, lbl3, lbl4, lbl5, lbl6 }, new[] { kw1, kw2, kw3, kw4, kw5, kw6 }) { } | ||
|  | 
 | ||
|  | 		public MaterialKeywordEnumNoPrefixDrawer(string[] labels, string[] keywords) | ||
|  | 		{ | ||
|  | 			this.labels= new GUIContent[keywords.Length]; | ||
|  | 			this.keywords = new string[keywords.Length]; | ||
|  | 			for (int i = 0; i < keywords.Length; ++i) | ||
|  | 			{ | ||
|  | 				this.labels[i] = new GUIContent(labels[i]); | ||
|  | 				this.keywords[i] = keywords[i]; | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		static bool IsPropertyTypeSuitable(MaterialProperty prop) | ||
|  | 		{ | ||
|  | 			return prop.type == MaterialProperty.PropType.Float || prop.type == MaterialProperty.PropType.Range; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		void SetKeyword(MaterialProperty prop, int index) | ||
|  | 		{ | ||
|  | 			for (int i = 0; i < keywords.Length; ++i) | ||
|  | 			{ | ||
|  | 				string keyword = GetKeywordName(prop.name, keywords[i]); | ||
|  | 				foreach (Material material in prop.targets) | ||
|  | 				{ | ||
|  | 					if (index == i) | ||
|  | 						material.EnableKeyword(keyword); | ||
|  | 					else | ||
|  | 						material.DisableKeyword(keyword); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) | ||
|  | 		{ | ||
|  | 			if (!IsPropertyTypeSuitable(prop)) | ||
|  | 			{ | ||
|  | 				return EditorGUIUtility.singleLineHeight * 2.5f; | ||
|  | 			} | ||
|  | 			return base.GetPropertyHeight(prop, label, editor); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) | ||
|  | 		{ | ||
|  | 			if (!IsPropertyTypeSuitable(prop)) | ||
|  | 			{ | ||
|  | 				EditorGUI.HelpBox(position, "Toggle used on a non-float property: " + prop.name, MessageType.Warning); | ||
|  | 				return; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			EditorGUI.BeginChangeCheck(); | ||
|  | 
 | ||
|  | 			EditorGUI.showMixedValue = prop.hasMixedValue; | ||
|  | 			var value = (int)prop.floatValue; | ||
|  | 			value = EditorGUI.Popup(position, label, value, labels); | ||
|  | 			EditorGUI.showMixedValue = false; | ||
|  | 			if (EditorGUI.EndChangeCheck()) | ||
|  | 			{ | ||
|  | 				prop.floatValue = value; | ||
|  | 				SetKeyword(prop, value); | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		public override void Apply(MaterialProperty prop) | ||
|  | 		{ | ||
|  | 			base.Apply(prop); | ||
|  | 			if (!IsPropertyTypeSuitable(prop)) | ||
|  | 				return; | ||
|  | 
 | ||
|  | 			if (prop.hasMixedValue) | ||
|  | 				return; | ||
|  | 
 | ||
|  | 			SetKeyword(prop, (int)prop.floatValue); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Final keyword name: property name + "_" + display name. Uppercased, | ||
|  | 		// and spaces replaced with underscores. | ||
|  | 		private static string GetKeywordName(string propName, string name) | ||
|  | 		{ | ||
|  | 			// Just return the supplied name | ||
|  | 			return name; | ||
|  | 
 | ||
|  | 			// Original code: | ||
|  | 			/* | ||
|  | 			string n = propName + "_" + name; | ||
|  | 			return n.Replace(' ', '_').ToUpperInvariant(); | ||
|  | 			*/ | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	//================================================================================================================================================================================================ | ||
|  | 	// GUI Commands System | ||
|  | 	// | ||
|  | 	// Workaround to Material Property Drawers limitations: | ||
|  | 	// - uses shader comments to organize the GUI, and show/hide properties based on conditions | ||
|  | 	// - can use any character (unlike property drawers) | ||
|  | 	// - parsed once at material editor initialization | ||
|  | 
 | ||
|  | 	internal class GUICommand | ||
|  | 	{ | ||
|  | 		public virtual bool Visible() { return true; } | ||
|  | 		public virtual void OnGUI() { } | ||
|  | 	} | ||
|  | 
 | ||
|  | 	internal class GC_Separator : GUICommand { public override void OnGUI() { if(MaterialInspector.ShowNextProperty) Styles.MaterialDrawSeparator(); } } | ||
|  | 	internal class GC_SeparatorDouble : GUICommand { public override void OnGUI() { if(MaterialInspector.ShowNextProperty) Styles.MaterialDrawSeparatorDouble(); } } | ||
|  | 	internal class GC_Space : GUICommand { public override void OnGUI() { if(MaterialInspector.ShowNextProperty) GUILayout.Space(8); } } | ||
|  | 	internal class GC_HelpBox : GUICommand | ||
|  | 	{ | ||
|  | 		public string message { get; set; } | ||
|  | 		public MessageType messageType { get; set; } | ||
|  | 
 | ||
|  | 		public override void OnGUI() | ||
|  | 		{ | ||
|  | 			if(MaterialInspector.ShowNextProperty) | ||
|  | 				Styles.HelpBoxRichText(message, messageType); | ||
|  | 		} | ||
|  | 	} | ||
|  | 	internal class GC_Header : GUICommand | ||
|  | 	{ | ||
|  | 		public string label { get; set; } | ||
|  | 		GUIContent guiContent; | ||
|  | 
 | ||
|  | 		public override void OnGUI() | ||
|  | 		{ | ||
|  | 			if(guiContent == null) | ||
|  | 				guiContent = new GUIContent(label); | ||
|  | 
 | ||
|  | 			if(MaterialInspector.ShowNextProperty) | ||
|  | 				Styles.MaterialDrawHeader(guiContent); | ||
|  | 		} | ||
|  | 	} | ||
|  | 	internal class GC_Label : GUICommand | ||
|  | 	{ | ||
|  | 		public string label { get; set; } | ||
|  | 		GUIContent guiContent; | ||
|  | 
 | ||
|  | 		public override void OnGUI() | ||
|  | 		{ | ||
|  | 			if(guiContent == null) | ||
|  | 				guiContent = new GUIContent(label); | ||
|  | 
 | ||
|  | 			if(MaterialInspector.ShowNextProperty) | ||
|  | 				GUILayout.Label(guiContent); | ||
|  | 		} | ||
|  | 	} | ||
|  | 	internal class GC_IfKeyword : GUICommand | ||
|  | 	{ | ||
|  | 		public string expression { get; set; } | ||
|  | 		public Material[] materials { get; set; } | ||
|  | 		public override void OnGUI() | ||
|  | 		{ | ||
|  | 			bool show = ExpressionParser.EvaluateExpression(expression, (string s) => | ||
|  | 			{ | ||
|  | 				foreach(var m in materials) | ||
|  | 				{ | ||
|  | 					if(m.IsKeywordEnabled(s)) | ||
|  | 						return true; | ||
|  | 				} | ||
|  | 				return false; | ||
|  | 			}); | ||
|  | 			MaterialInspector.PushShowProperty(show); | ||
|  | 		} | ||
|  | 	} | ||
|  | 	internal class GC_EndIf : GUICommand { public override void OnGUI() { MaterialInspector.PopShowProperty(); } } | ||
|  | 
 | ||
|  | 	internal class GC_IfProperty : GUICommand | ||
|  | 	{ | ||
|  | 		string _expression; | ||
|  | 		public string expression | ||
|  | 		{ | ||
|  | 			get { return _expression; } | ||
|  | 			set { _expression = value.Replace("!=", "<>"); } | ||
|  | 		} | ||
|  | 		public Material[] materials { get; set; } | ||
|  | 
 | ||
|  | 		public override void OnGUI() | ||
|  | 		{ | ||
|  | 			bool show = ExpressionParser.EvaluateExpression(expression, EvaluatePropertyExpression); | ||
|  | 			MaterialInspector.PushShowProperty(show); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		bool EvaluatePropertyExpression(string expr) | ||
|  | 		{ | ||
|  | 			//expression is expected to be in the form of: property operator value | ||
|  | 			var reader = new StringReader(expr); | ||
|  | 			string property = ""; | ||
|  | 			string op = ""; | ||
|  | 			float value = 0f; | ||
|  | 
 | ||
|  | 			int overflow = 0; | ||
|  | 			while(true) | ||
|  | 			{ | ||
|  | 				char c = (char)reader.Read(); | ||
|  | 
 | ||
|  | 				//operator | ||
|  | 				if(c == '=' || c == '>' || c == '<' || c == '!') | ||
|  | 				{ | ||
|  | 					op += c; | ||
|  | 					//second operator character, if any | ||
|  | 					char c2 = (char)reader.Peek(); | ||
|  | 					if(c2 == '=' || c2 == '>') | ||
|  | 					{ | ||
|  | 						reader.Read(); | ||
|  | 						op += c2; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					//end of string is the value | ||
|  | 					var end = reader.ReadToEnd(); | ||
|  | 					if(!float.TryParse(end, out value)) | ||
|  | 					{ | ||
|  | 						Debug.LogError("Couldn't parse float from property expression:\n" + end); | ||
|  | 						return false; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					break; | ||
|  | 				} | ||
|  | 
 | ||
|  | 				//property name | ||
|  | 				property += c; | ||
|  | 
 | ||
|  | 				overflow++; | ||
|  | 				if(overflow >= 9999) | ||
|  | 				{ | ||
|  | 					Debug.LogError("Expression parsing overflow!\n"); | ||
|  | 					return false; | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			//evaluate property | ||
|  | 			bool conditionMet = false; | ||
|  | 			foreach(var m in materials) | ||
|  | 			{ | ||
|  | 				float propValue = 0f; | ||
|  | 				if(property.Contains(".x") || property.Contains(".y") || property.Contains(".z") || property.Contains(".w")) | ||
|  | 				{ | ||
|  | 					string[] split = property.Split('.'); | ||
|  | 					string component = split[1]; | ||
|  | 					switch(component) | ||
|  | 					{ | ||
|  | 						case "x": propValue = m.GetVector(split[0]).x; break; | ||
|  | 						case "y": propValue = m.GetVector(split[0]).y; break; | ||
|  | 						case "z": propValue = m.GetVector(split[0]).z; break; | ||
|  | 						case "w": propValue = m.GetVector(split[0]).w; break; | ||
|  | 						default: Debug.LogError("Invalid component for vector property: '" + property + "'"); break; | ||
|  | 					} | ||
|  | 				} | ||
|  | 				else | ||
|  | 					propValue = m.GetFloat(property); | ||
|  | 
 | ||
|  | 				switch(op) | ||
|  | 				{ | ||
|  | 					case ">=": conditionMet = propValue >= value; break; | ||
|  | 					case "<=": conditionMet = propValue <= value; break; | ||
|  | 					case ">": conditionMet = propValue > value; break; | ||
|  | 					case "<": conditionMet = propValue < value; break; | ||
|  | 					case "<>": conditionMet = propValue != value; break;	//not equal, "!=" is replaced by "<>" to prevent bug with leading ! ("not" operator) | ||
|  | 					case "==": conditionMet = propValue == value; break; | ||
|  | 					default: | ||
|  | 						Debug.LogError("Invalid property expression:\n" + expr); | ||
|  | 						break; | ||
|  | 				} | ||
|  | 				if(conditionMet) | ||
|  | 					return true; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			return false; | ||
|  | 		} | ||
|  | 	} | ||
|  | } |