623 lines
21 KiB
C#
623 lines
21 KiB
C#
using UnityEngine;
|
|
using UnityEditor;
|
|
|
|
namespace TMPro.EditorUtilities
|
|
{
|
|
/// <summary>Base class for TextMesh Pro shader GUIs.</summary>
|
|
public abstract class TMP_BaseShaderGUI : ShaderGUI
|
|
{
|
|
/// <summary>Representation of a #pragma shader_feature.</summary>
|
|
/// <description>It is assumed that the first feature option is for no keyword (underscores).</description>
|
|
protected class ShaderFeature
|
|
{
|
|
public string undoLabel;
|
|
|
|
public GUIContent label;
|
|
|
|
/// <summary>The keyword labels, for display. Include the no-keyword as the first option.</summary>
|
|
public GUIContent[] keywordLabels;
|
|
|
|
/// <summary>The shader keywords. Exclude the no-keyword option.</summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>Override this method to create the specific shader GUI.</summary>
|
|
protected abstract void DoGUI();
|
|
|
|
static string[] s_PanelStateLabel = new string[] { "\t- <i>Click to collapse</i> -", "\t- <i>Click to expand</i> -" };
|
|
|
|
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<TMP_Text>();
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|