using UnityEngine; using UnityEditor; namespace TMPro.EditorUtilities { /// Base class for TextMesh Pro shader GUIs. public abstract class TMP_BaseShaderGUI : ShaderGUI { /// Representation of a #pragma shader_feature. /// It is assumed that the first feature option is for no keyword (underscores). protected class ShaderFeature { public string undoLabel; public GUIContent label; /// The keyword labels, for display. Include the no-keyword as the first option. public GUIContent[] keywordLabels; /// The shader keywords. Exclude the no-keyword option. public string[] keywords; int m_State; public bool Active { get { return m_State >= 0; } } public int State { get { return m_State; } } public void ReadState(Material material) { for (int i = 0; i < keywords.Length; i++) { if (material.IsKeywordEnabled(keywords[i])) { m_State = i; return; } } m_State = -1; } public void SetActive(bool active, Material material) { m_State = active ? 0 : -1; SetStateKeywords(material); } public void DoPopup(MaterialEditor editor, Material material) { EditorGUI.BeginChangeCheck(); int selection = EditorGUILayout.Popup(label, m_State + 1, keywordLabels); if (EditorGUI.EndChangeCheck()) { m_State = selection - 1; editor.RegisterPropertyChangeUndo(undoLabel); SetStateKeywords(material); } } void SetStateKeywords(Material material) { for (int i = 0; i < keywords.Length; i++) { if (i == m_State) { material.EnableKeyword(keywords[i]); } else { material.DisableKeyword(keywords[i]); } } } } static GUIContent s_TempLabel = new GUIContent(); protected static bool s_DebugExtended; static int s_UndoRedoCount, s_LastSeenUndoRedoCount; static float[][] s_TempFloats = { null, new float[1], new float[2], new float[3], new float[4] }; protected static GUIContent[] s_XywhVectorLabels = { new GUIContent("X"), new GUIContent("Y"), new GUIContent("W", "Width"), new GUIContent("H", "Height") }; protected static GUIContent[] s_LbrtVectorLabels = { new GUIContent("L", "Left"), new GUIContent("B", "Bottom"), new GUIContent("R", "Right"), new GUIContent("T", "Top") }; protected static GUIContent[] s_CullingTypeLabels = { new GUIContent("Off"), new GUIContent("Front"), new GUIContent("Back") }; static TMP_BaseShaderGUI() { // Keep track of how many undo/redo events happened. Undo.undoRedoPerformed += () => s_UndoRedoCount += 1; } bool m_IsNewGUI = true; float m_DragAndDropMinY; protected MaterialEditor m_Editor; protected Material m_Material; protected MaterialProperty[] m_Properties; void PrepareGUI() { m_IsNewGUI = false; ShaderUtilities.GetShaderPropertyIDs(); // New GUI just got constructed. This happens in response to a selection, // but also after undo/redo events. if (s_LastSeenUndoRedoCount != s_UndoRedoCount) { // There's been at least one undo/redo since the last time this GUI got constructed. // Maybe the undo/redo was for this material? Assume that is was. TMPro_EventManager.ON_MATERIAL_PROPERTY_CHANGED(true, m_Material as Material); } s_LastSeenUndoRedoCount = s_UndoRedoCount; } public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) { m_Editor = materialEditor; m_Material = materialEditor.target as Material; this.m_Properties = properties; if (m_IsNewGUI) { PrepareGUI(); } DoDragAndDropBegin(); EditorGUI.BeginChangeCheck(); DoGUI(); if (EditorGUI.EndChangeCheck()) { TMPro_EventManager.ON_MATERIAL_PROPERTY_CHANGED(true, m_Material); } DoDragAndDropEnd(); } /// Override this method to create the specific shader GUI. protected abstract void DoGUI(); static string[] s_PanelStateLabel = new string[] { "\t- Click to collapse -", "\t- Click to expand -" }; protected bool BeginPanel(string panel, bool expanded) { EditorGUI.indentLevel = 0; EditorGUILayout.BeginVertical(EditorStyles.helpBox); Rect r = EditorGUI.IndentedRect(GUILayoutUtility.GetRect(20, 18)); r.x += 20; r.width += 6; bool enabled = GUI.enabled; GUI.enabled = true; expanded = TMP_EditorUtility.EditorToggle(r, expanded, new GUIContent(panel), TMP_UIStyleManager.panelTitle); r.width -= 30; EditorGUI.LabelField(r, new GUIContent(expanded ? s_PanelStateLabel[0] : s_PanelStateLabel[1]), TMP_UIStyleManager.rightLabel); GUI.enabled = enabled; EditorGUI.indentLevel += 1; EditorGUI.BeginDisabledGroup(false); return expanded; } protected bool BeginPanel(string panel, ShaderFeature feature, bool expanded, bool readState = true) { EditorGUI.indentLevel = 0; if (readState) { feature.ReadState(m_Material); } EditorGUI.BeginChangeCheck(); EditorGUILayout.BeginVertical(EditorStyles.helpBox); GUILayout.BeginHorizontal(); Rect r = EditorGUI.IndentedRect(GUILayoutUtility.GetRect(20, 20, GUILayout.Width(20f))); bool active = EditorGUI.Toggle(r, feature.Active); if (EditorGUI.EndChangeCheck()) { m_Editor.RegisterPropertyChangeUndo(feature.undoLabel); feature.SetActive(active, m_Material); } r = EditorGUI.IndentedRect(GUILayoutUtility.GetRect(20, 18)); r.width += 6; bool enabled = GUI.enabled; GUI.enabled = true; expanded = TMP_EditorUtility.EditorToggle(r, expanded, new GUIContent(panel), TMP_UIStyleManager.panelTitle); r.width -= 10; EditorGUI.LabelField(r, new GUIContent(expanded ? s_PanelStateLabel[0] : s_PanelStateLabel[1]), TMP_UIStyleManager.rightLabel); GUI.enabled = enabled; GUILayout.EndHorizontal(); EditorGUI.indentLevel += 1; EditorGUI.BeginDisabledGroup(!active); return expanded; } public void EndPanel() { EditorGUI.EndDisabledGroup(); EditorGUI.indentLevel -= 1; EditorGUILayout.EndVertical(); } MaterialProperty BeginProperty(string name) { MaterialProperty property = FindProperty(name, m_Properties); EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = property.hasMixedValue; m_Editor.BeginAnimatedCheck(Rect.zero, property); return property; } bool EndProperty() { m_Editor.EndAnimatedCheck(); EditorGUI.showMixedValue = false; return EditorGUI.EndChangeCheck(); } protected void DoPopup(string name, string label, GUIContent[] options) { MaterialProperty property = BeginProperty(name); s_TempLabel.text = label; int index = EditorGUILayout.Popup(s_TempLabel, (int)property.floatValue, options); if (EndProperty()) { property.floatValue = index; } } protected void DoCubeMap(string name, string label) { DoTexture(name, label, typeof(Cubemap)); } protected void DoTexture2D(string name, string label, bool withTilingOffset = false, string[] speedNames = null) { DoTexture(name, label, typeof(Texture2D), withTilingOffset, speedNames); } void DoTexture(string name, string label, System.Type type, bool withTilingOffset = false, string[] speedNames = null) { float objFieldSize = 60f; bool smallLayout = EditorGUIUtility.currentViewWidth <= 440f && (withTilingOffset || speedNames != null); float controlHeight = smallLayout ? objFieldSize * 2 : objFieldSize; MaterialProperty property = FindProperty(name, m_Properties); m_Editor.BeginAnimatedCheck(Rect.zero, property); Rect rect = EditorGUILayout.GetControlRect(true, controlHeight); float totalWidth = rect.width; rect.width = EditorGUIUtility.labelWidth + objFieldSize; rect.height = objFieldSize; s_TempLabel.text = label; EditorGUI.BeginChangeCheck(); Object tex = EditorGUI.ObjectField(rect, s_TempLabel, property.textureValue, type, false); if (EditorGUI.EndChangeCheck()) { property.textureValue = tex as Texture; } float additionalHeight = controlHeight - objFieldSize; float xOffset = smallLayout ? rect.width - objFieldSize : rect.width; rect.y += additionalHeight; rect.x += xOffset; rect.width = totalWidth - xOffset; rect.height = EditorGUIUtility.singleLineHeight; if (withTilingOffset) { DoTilingOffset(rect, property); rect.y += (rect.height + 2f) * 2f; } m_Editor.EndAnimatedCheck(); if (speedNames != null) { DoUVSpeed(rect, speedNames); } } void DoTilingOffset(Rect rect, MaterialProperty property) { float labelWidth = EditorGUIUtility.labelWidth; int indentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; EditorGUIUtility.labelWidth = Mathf.Min(37f, rect.width * 0.40f); Vector4 vector = property.textureScaleAndOffset; bool changed = false; float[] values = s_TempFloats[2]; s_TempLabel.text = "Tiling"; Rect vectorRect = EditorGUI.PrefixLabel(rect, s_TempLabel); values[0] = vector.x; values[1] = vector.y; EditorGUI.BeginChangeCheck(); EditorGUI.MultiFloatField(vectorRect, s_XywhVectorLabels, values); if (EditorGUI.EndChangeCheck()) { vector.x = values[0]; vector.y = values[1]; changed = true; } rect.y += rect.height + 2f; s_TempLabel.text = "Offset"; vectorRect = EditorGUI.PrefixLabel(rect, s_TempLabel); values[0] = vector.z; values[1] = vector.w; EditorGUI.BeginChangeCheck(); EditorGUI.MultiFloatField(vectorRect, s_XywhVectorLabels, values); if (EditorGUI.EndChangeCheck()) { vector.z = values[0]; vector.w = values[1]; changed = true; } if (changed) { property.textureScaleAndOffset = vector; } EditorGUIUtility.labelWidth = labelWidth; EditorGUI.indentLevel = indentLevel; } protected void DoUVSpeed(Rect rect, string[] names) { float labelWidth = EditorGUIUtility.labelWidth; int indentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; EditorGUIUtility.labelWidth = Mathf.Min(37f, rect.width * 0.40f); s_TempLabel.text = "Speed"; rect = EditorGUI.PrefixLabel(rect, s_TempLabel); EditorGUIUtility.labelWidth = 10f; rect.width = rect.width * 0.5f - 2f; if (names.Length == 1) { DoFloat2(rect, names[0]); } else { DoFloat(rect, names[0], "X"); rect.x += rect.width + 4f; DoFloat(rect, names[1], "Y"); } EditorGUIUtility.labelWidth = labelWidth; EditorGUI.indentLevel = indentLevel; } protected void DoToggle(string name, string label) { MaterialProperty property = BeginProperty(name); s_TempLabel.text = label; bool value = EditorGUILayout.Toggle(s_TempLabel, property.floatValue == 1f); if (EndProperty()) { property.floatValue = value ? 1f : 0f; } } protected void DoFloat(string name, string label) { MaterialProperty property = BeginProperty(name); Rect rect = EditorGUILayout.GetControlRect(); rect.width = EditorGUIUtility.labelWidth + 55f; s_TempLabel.text = label; float value = EditorGUI.FloatField(rect, s_TempLabel, property.floatValue); if (EndProperty()) { property.floatValue = value; } } protected void DoColor(string name, string label) { MaterialProperty property = BeginProperty(name); s_TempLabel.text = label; Color value = EditorGUI.ColorField(EditorGUILayout.GetControlRect(), s_TempLabel, property.colorValue, false, true, true); if (EndProperty()) { property.colorValue = value; } } void DoFloat(Rect rect, string name, string label) { MaterialProperty property = BeginProperty(name); s_TempLabel.text = label; float value = EditorGUI.FloatField(rect, s_TempLabel, property.floatValue); if (EndProperty()) { property.floatValue = value; } } void DoFloat2(Rect rect, string name) { MaterialProperty property = BeginProperty(name); float x = EditorGUI.FloatField(rect, "X", property.vectorValue.x); rect.x += rect.width + 4f; float y = EditorGUI.FloatField(rect, "Y", property.vectorValue.y); if (EndProperty()) { property.vectorValue = new Vector2(x, y); } } protected void DoSlider(string name, string label) { MaterialProperty property = BeginProperty(name); Vector2 range = property.rangeLimits; s_TempLabel.text = label; float value = EditorGUI.Slider(EditorGUILayout.GetControlRect(), s_TempLabel, property.floatValue, range.x, range.y); if (EndProperty()) { property.floatValue = value; } } protected void DoSlider(string propertyName, string propertyField, string label) { MaterialProperty property = BeginProperty(propertyName); Vector2 range = property.rangeLimits; s_TempLabel.text = label; Vector4 value = property.vectorValue; switch (propertyField) { case "X": value.x = EditorGUI.Slider(EditorGUILayout.GetControlRect(), s_TempLabel, value.x, range.x, range.y); break; case "Y": value.y = EditorGUI.Slider(EditorGUILayout.GetControlRect(), s_TempLabel, value.y, range.x, range.y); break; case "Z": value.z = EditorGUI.Slider(EditorGUILayout.GetControlRect(), s_TempLabel, value.z, range.x, range.y); break; case "W": value.w = EditorGUI.Slider(EditorGUILayout.GetControlRect(), s_TempLabel, value.w, range.x, range.y); break; } if (EndProperty()) { property.vectorValue = value; } } protected void DoVector2(string name, string label) { MaterialProperty property = BeginProperty(name); s_TempLabel.text = label; Vector4 value = EditorGUILayout.Vector3Field(s_TempLabel, property.vectorValue); if (EndProperty()) { property.vectorValue = value; } } protected void DoVector3(string name, string label) { MaterialProperty property = BeginProperty(name); s_TempLabel.text = label; Vector4 value = EditorGUILayout.Vector3Field(s_TempLabel, property.vectorValue); if (EndProperty()) { property.vectorValue = value; } } protected void DoVector(string name, string label, GUIContent[] subLabels) { MaterialProperty property = BeginProperty(name); Rect rect = EditorGUILayout.GetControlRect(); s_TempLabel.text = label; rect = EditorGUI.PrefixLabel(rect, s_TempLabel); Vector4 vector = property.vectorValue; float[] values = s_TempFloats[subLabels.Length]; for (int i = 0; i < subLabels.Length; i++) { values[i] = vector[i]; } EditorGUI.MultiFloatField(rect, subLabels, values); if (EndProperty()) { for (int i = 0; i < subLabels.Length; i++) { vector[i] = values[i]; } property.vectorValue = vector; } } void DoDragAndDropBegin() { m_DragAndDropMinY = GUILayoutUtility.GetRect(0f, 0f, GUILayout.ExpandWidth(true)).y; } void DoDragAndDropEnd() { Rect rect = GUILayoutUtility.GetRect(0f, 0f, GUILayout.ExpandWidth(true)); Event evt = Event.current; if (evt.type == EventType.DragUpdated) { DragAndDrop.visualMode = DragAndDropVisualMode.Generic; evt.Use(); } else if ( evt.type == EventType.DragPerform && Rect.MinMaxRect(rect.xMin, m_DragAndDropMinY, rect.xMax, rect.yMax).Contains(evt.mousePosition) ) { DragAndDrop.AcceptDrag(); evt.Use(); Material droppedMaterial = DragAndDrop.objectReferences[0] as Material; if (droppedMaterial && droppedMaterial != m_Material) { PerformDrop(droppedMaterial); } } } void PerformDrop(Material droppedMaterial) { Texture droppedTex = droppedMaterial.GetTexture(ShaderUtilities.ID_MainTex); if (!droppedTex) { return; } Texture currentTex = m_Material.GetTexture(ShaderUtilities.ID_MainTex); TMP_FontAsset requiredFontAsset = null; if (droppedTex != currentTex) { requiredFontAsset = TMP_EditorUtility.FindMatchingFontAsset(droppedMaterial); if (!requiredFontAsset) { return; } } foreach (GameObject o in Selection.gameObjects) { if (requiredFontAsset) { TMP_Text textComponent = o.GetComponent(); if (textComponent) { Undo.RecordObject(textComponent, "Font Asset Change"); textComponent.font = requiredFontAsset; } } TMPro_EventManager.ON_DRAG_AND_DROP_MATERIAL_CHANGED(o, m_Material, droppedMaterial); EditorUtility.SetDirty(o); } } } }