// ===================================================================== // Copyright 2013-2017 Fluffy Underware // All rights reserved // // http://www.fluffyunderware.com // ===================================================================== #if UNITY_5_4 || UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0 #define UNITY_PRE_5_5 #endif using System; using UnityEngine; using UnityEditor; using FluffyUnderware.DevTools; using FluffyUnderware.DevTools.Extensions; using FluffyUnderware.DevToolsEditor.Extensions; using System.Reflection; using System.Collections.Generic; using System.Linq; using System.Collections; using MinAttribute = FluffyUnderware.DevTools.MinAttribute; namespace FluffyUnderware.DevToolsEditor { public class DTPropertyDrawer : PropertyDrawer where T : DTPropertyAttribute { /// /// Gets the attribute /// protected T Attribute { get { return attribute as T; } } /// /// Gets some common attribute options /// protected AttributeOptionsFlags Options { get { return Attribute.Options; } } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { if (!string.IsNullOrEmpty(Attribute.Label)) label.text = Attribute.Label; label.tooltip = Attribute.Tooltip; } protected void PushCustomColors() { if (!string.IsNullOrEmpty(Attribute.Color)) DTGUI.PushColor(Attribute.Color.ColorFromHtml()); } protected void PopCustomColors() { if (!string.IsNullOrEmpty(Attribute.Color)) DTGUI.PopColor(); } protected MemberInfo GetMemberInfo(SerializedProperty property, string fieldOrPropertyName, out object parent) { parent = property.GetParentObject(); Type parentType = parent.GetType(); FieldInfo fi = parentType.FieldByName(fieldOrPropertyName, true, true); if (fi != null) return fi; else { PropertyInfo pi = parentType.PropertyByName(fieldOrPropertyName, true, true); if (pi != null) return pi; else { DTLog.LogError(parentType.Name + ": Field or Property '" + fieldOrPropertyName + "' not found!"); return null; } } } /// /// Get a value from a field or property of the serialized object by it's name /// /// datatype to retrieve /// a serialized property from the object in question /// name of the field or property /// the value of the field or property protected U GetMemberValue(SerializedProperty property, string fieldOrPropertyName) { object parent; object o = null; MemberInfo mi = GetMemberInfo(property, fieldOrPropertyName, out parent); if (mi != null) { if (mi is FieldInfo) o = ((FieldInfo)mi).GetValue(parent); else o = ((PropertyInfo)mi).GetValue(parent, null); } Type oT = o.GetType(); Type rT = typeof(U); if (oT == rT) return (U)o; else if (oT == typeof(int) && rT == typeof(float)) return (U)System.Convert.ChangeType(o, rT, System.Globalization.CultureInfo.InvariantCulture); else return default(U); } protected object GetParent(SerializedProperty prop) { string path = prop.propertyPath.Replace(".Array.data[", "["); object obj = prop.serializedObject.targetObject; string[] elements = path.Split('.'); foreach (string element in elements.Take(elements.Length - 1)) { if (element.Contains("[")) { string elementName = element.Substring(0, element.IndexOf("[")); int index = System.Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", "")); obj = GetValue(obj, elementName, index); } else { obj = GetValue(obj, element); } } return obj; } object GetValue(object source, string name) { if (source == null) return null; Type type = source.GetType(); FieldInfo f = type.FieldByName(name, true, true); if (f == null) { PropertyInfo p = type.PropertyByName(name, true, true); if (p == null) return null; return p.GetValue(source, null); } return f.GetValue(source); } object GetValue(object source, string name, int index) { IEnumerable enumerable = GetValue(source, name) as IEnumerable; IEnumerator enm = enumerable.GetEnumerator(); while (index-- >= 0) enm.MoveNext(); return enm.Current; } protected U GetPropertySourceField(SerializedProperty property) { return (U)fieldInfo.GetValue(GetParent(property)); /* var instance = property.serializedObject.targetObject; var t = instance.GetType(); FieldInfo fi = null; while (fi == null && t != null) { fi = t.GetField(property.propertyPath, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (fi == null) t = t.BaseType; } return (fi != null) ? (U)fi.GetValue(instance) : default(U); */ } } [CustomPropertyDrawer(typeof(LabelAttribute))] public class LabelAttributePropertyDrawer : DTPropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { base.OnGUI(position, property, label); EditorGUI.PropertyField(position, property, label); } } [CustomPropertyDrawer(typeof(ToggleButtonAttribute))] public class ToggleButtonAttributePropertyDrawer : DTPropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { base.OnGUI(position, property, label); if (property.propertyType != SerializedPropertyType.Boolean) DTLog.LogError("[DevTools] - [ToggleButton] only valid for boolean fields!"); else { EditorGUI.BeginChangeCheck(); GUIStyle st = GUI.skin.button; bool v = GUI.Toggle(position, property.boolValue, label, st); if (EditorGUI.EndChangeCheck()) property.boolValue = v; } } } [CustomPropertyDrawer(typeof(LayerAttribute))] public class LayerPropertyDrawer : DTPropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, GUIContent.none, property); position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); property.intValue = EditorGUI.LayerField(position, property.intValue); EditorGUI.EndProperty(); } } [CustomPropertyDrawer(typeof(TagAttribute))] public class TagPropertyDrawer : DTPropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, GUIContent.none, property); position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); property.stringValue = EditorGUI.TagField(position, property.stringValue); EditorGUI.EndProperty(); } } [CustomPropertyDrawer(typeof(VectorExAttribute))] public class VectorExPropertyDrawer : DTPropertyDrawer { private static readonly GUIContent CopyButtonGuiContent = new GUIContent("C", "Copy"); private static readonly GUIContent PasteButtonGuiContent = new GUIContent("P", "Past"); private static readonly GUIContent SetZeroButtonGuiContent = new GUIContent("0", "Set to zero"); private static readonly GUIContent SetOneButtonGuiContent = new GUIContent("1", "Set to one"); private static readonly GUIContent NegateButtonGuiContent = new GUIContent("~", "Negate"); public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { float h; if (Options.HasFlag(AttributeOptionsFlags.Compact) || EditorGUIUtility.wideMode) h = base.GetPropertyHeight(property, label); else h = base.GetPropertyHeight(property, label) * 2; return h; } public static void DoGUI(Rect position, SerializedProperty property, DTPropertyAttribute attribute, GUIContent label, AttributeOptionsFlags flags, VectorExPropertyDrawer drawer = null) { if (property.propertyType != SerializedPropertyType.Vector2 && property.propertyType != SerializedPropertyType.Vector3 && property.propertyType != SerializedPropertyType.Quaternion) DTLog.LogError("[DevTools] - [VectorEx] only valid for Vector and Quaternion fields!"); else { int buttons = 0; if (flags.HasFlag(AttributeOptionsFlags.Clipboard)) buttons += 2; if (flags.HasFlag(AttributeOptionsFlags.One)) buttons++; if (flags.HasFlag(AttributeOptionsFlags.Zero)) buttons++; if (flags.HasFlag(AttributeOptionsFlags.Negate)) buttons++; int buttonWidth = 21; int interButtonSpace = 1; int buttonsWidth = buttons * buttonWidth + Math.Max(0, buttons - 1) * interButtonSpace; Rect buttonsRectangle = new Rect(); buttonsRectangle.x = position.x + position.width - buttonsWidth; buttonsRectangle.y = position.y; buttonsRectangle.width = buttonWidth; buttonsRectangle.height = EditorGUIUtility.singleLineHeight; Rect r = position.WithoutLabel(); r.width -= buttonsWidth; if (flags.HasFlag(AttributeOptionsFlags.Compact) || EditorGUIUtility.wideMode) { if (drawer != null) drawer.PushCustomColors(); EditorGUI.PrefixLabel(position, label); if (property.propertyType == SerializedPropertyType.Vector2) { EditorGUI.BeginChangeCheck(); Vector2 v = DTGUI.CompactVector2Field(r, property.vector2Value); if (EditorGUI.EndChangeCheck()) property.vector2Value = DT._UseSnapValuePrecision ? DTMath.SnapPrecision(v, attribute.Precision) : v; } else if (property.propertyType == SerializedPropertyType.Vector3) { EditorGUI.BeginChangeCheck(); Vector3 v = DTGUI.CompactVector3Field(r, property.vector3Value); if (EditorGUI.EndChangeCheck()) property.vector3Value = DT._UseSnapValuePrecision ? DTMath.SnapPrecision(v, attribute.Precision) : v; } else if (property.propertyType == SerializedPropertyType.Quaternion) { EditorGUI.BeginChangeCheck(); Vector3 v = DTGUI.CompactVector3Field(r, DT._UseSnapValuePrecision ? DTMath.SnapPrecision(property.quaternionValue.eulerAngles, attribute.Precision) : property.quaternionValue.eulerAngles); if (EditorGUI.EndChangeCheck()) property.quaternionValue = Quaternion.Euler(v); } if (drawer != null) drawer.PopCustomColors(); } else { EditorGUI.BeginChangeCheck(); if (drawer != null) drawer.PushCustomColors(); if (DTEditorUtility.UsesNewEditorUI) #pragma warning disable 162 position.y -= 2; #pragma warning restore 162 EditorGUI.PropertyField(position, property, label); if (drawer != null) drawer.PopCustomColors(); if (EditorGUI.EndChangeCheck() && DT._UseSnapValuePrecision) { if (property.propertyType == SerializedPropertyType.Vector2) property.vector2Value = DTMath.SnapPrecision(property.vector2Value, attribute.Precision); else if (property.propertyType == SerializedPropertyType.Vector3) property.vector3Value = DTMath.SnapPrecision(property.vector3Value, attribute.Precision); } } if (flags.HasFlag(AttributeOptionsFlags.Clipboard)) { if (GUI.Button(buttonsRectangle, CopyButtonGuiContent, EditorStyles.miniButton)) if (property.propertyType == SerializedPropertyType.Vector2) DT.ClipboardCopy(property.vector2Value); else if (property.propertyType == SerializedPropertyType.Vector3) DT.ClipboardCopy(property.vector3Value); buttonsRectangle.x += buttonWidth + interButtonSpace; // Can Paste? if (property.propertyType == SerializedPropertyType.Vector2) GUI.enabled = DT.ClipboardCanPasteTo(); else if (property.propertyType == SerializedPropertyType.Vector3) GUI.enabled = DT.ClipboardCanPasteTo(); if (GUI.Button(buttonsRectangle, PasteButtonGuiContent, EditorStyles.miniButton)) if (property.propertyType == SerializedPropertyType.Vector2) property.vector2Value = DT.ClipboardPaste(); else if (property.propertyType == SerializedPropertyType.Vector3) property.vector3Value = DT.ClipboardPaste(); GUI.enabled = true; buttonsRectangle.x += buttonWidth + interButtonSpace; } if (flags.HasFlag(AttributeOptionsFlags.Zero)) { if (GUI.Button(buttonsRectangle, SetZeroButtonGuiContent, EditorStyles.miniButton)) if (property.propertyType == SerializedPropertyType.Vector2) property.vector2Value = Vector2.zero; else if (property.propertyType == SerializedPropertyType.Vector3) property.vector3Value = Vector3.zero; buttonsRectangle.x += buttonWidth + interButtonSpace; } if (flags.HasFlag(AttributeOptionsFlags.One)) { if (GUI.Button(buttonsRectangle, SetOneButtonGuiContent, EditorStyles.miniButton)) if (property.propertyType == SerializedPropertyType.Vector2) property.vector2Value = Vector2.one; else if (property.propertyType == SerializedPropertyType.Vector3) property.vector3Value = Vector3.one; buttonsRectangle.x += buttonWidth + interButtonSpace; } if (flags.HasFlag(AttributeOptionsFlags.Negate)) { if (GUI.Button(buttonsRectangle, NegateButtonGuiContent, EditorStyles.miniButton)) if (property.propertyType == SerializedPropertyType.Vector2) property.vector2Value *= -1; else if (property.propertyType == SerializedPropertyType.Vector3) property.vector3Value *= -1; buttonsRectangle.x += buttonWidth + interButtonSpace; } } } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { base.OnGUI(position, property, label); DoGUI(position, property, Attribute, label, Options, this); } } [CustomPropertyDrawer(typeof(AnimationCurveExAttribute))] public class AnimationCurveExPropertyDrawer : DTPropertyDrawer { public override void OnGUI(Rect propertyRectangle, SerializedProperty property, GUIContent label) { base.OnGUI(propertyRectangle, property, label); if (property.propertyType != SerializedPropertyType.AnimationCurve) DTLog.LogError("[DevTools] - [AnimationCurvEx] only valid for AnimationCurve fields!"); else { int buttons = 0; if (Options.HasFlag(AttributeOptionsFlags.Clipboard)) buttons += 2; int buttonWidth = 21; int interButtonSpace = 1; int buttonsWidth = buttons * buttonWidth + Math.Max(0, buttons - 1) * interButtonSpace; Rect buttonsRectangle = new Rect(); buttonsRectangle.x = propertyRectangle.x + propertyRectangle.width - buttonsWidth; buttonsRectangle.y = propertyRectangle.y; buttonsRectangle.width = buttonWidth; buttonsRectangle.height = EditorGUIUtility.singleLineHeight; propertyRectangle.width -= buttonsWidth; EditorGUI.PropertyField(propertyRectangle, property, label); // Buttons if (Options.HasFlag(AttributeOptionsFlags.Clipboard)) { if (GUI.Button(buttonsRectangle, "C", EditorStyles.miniButton)) DT.ClipboardCopy(property.animationCurveValue); buttonsRectangle.x += buttonWidth + interButtonSpace; GUI.enabled = DT.ClipboardCanPasteTo(); if (GUI.Button(buttonsRectangle, "P", EditorStyles.miniButton)) property.animationCurveValue = DT.ClipboardPaste(); GUI.enabled = true; } } } } [CustomPropertyDrawer(typeof(RangeExAttribute))] public class RangeExPropertyDrawer : DTPropertyDrawer { float minV; float maxV; public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { base.OnGUI(position, property, label); minV = Attribute.MinValue; maxV = Attribute.MaxValue; if (!string.IsNullOrEmpty(Attribute.MinFieldOrPropertyName)) { minV = GetMemberValue(property, Attribute.MinFieldOrPropertyName); } if (!string.IsNullOrEmpty(Attribute.MaxFieldOrPropertyName)) { maxV = GetMemberValue(property, Attribute.MaxFieldOrPropertyName); } if (minV > maxV) { float temp = minV; minV = maxV; maxV = temp; } // GUI PushCustomColors(); EditorGUI.BeginChangeCheck(); if (Attribute.Slider) { switch (property.propertyType) { case SerializedPropertyType.Float: EditorGUI.Slider(position, property, minV, maxV, label); break; case SerializedPropertyType.Integer: EditorGUI.IntSlider(position, property, (int)minV, (int)maxV, label); break; } } else EditorGUI.PropertyField(position, property, label); if (EditorGUI.EndChangeCheck()) { // Clamp switch (property.propertyType) { case SerializedPropertyType.Float: if (DT._UseSnapValuePrecision && Attribute.Precision > -1) property.floatValue = DTMath.SnapPrecision(property.floatValue, Attribute.Precision); if (property.floatValue < minV || property.floatValue > maxV) property.floatValue = Mathf.Clamp(property.floatValue, minV, maxV); break; case SerializedPropertyType.Integer: if (property.intValue < minV || property.intValue > maxV) property.intValue = Mathf.Clamp(property.intValue, (int)minV, (int)maxV); break; } } PopCustomColors(); label.tooltip = ""; } } [CustomPropertyDrawer(typeof(FluffyUnderware.DevTools.MinMaxAttribute))] public class MinMaxPropertyDrawer : DTPropertyDrawer { float lBound; float uBound; public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { base.OnGUI(position, property, label); lBound = Attribute.Min; uBound = Attribute.Max; // Get maxV serialized string[] path = property.Path(); path[path.Length - 1] = Attribute.MaxValueField; SerializedProperty pMaxV = property.serializedObject.FindProperty(string.Join(".", path)); if (!string.IsNullOrEmpty(Attribute.MinBoundFieldOrPropertyName)) { lBound = GetMemberValue(property, Attribute.MinBoundFieldOrPropertyName); } if (!string.IsNullOrEmpty(Attribute.MaxBoundFieldOrPropertyName)) { uBound = GetMemberValue(property, Attribute.MaxBoundFieldOrPropertyName); } if (lBound > uBound) { float temp = lBound; lBound = uBound; uBound = temp; } float minV = 0; float maxV = 0; // GUI EditorGUI.BeginChangeCheck(); switch (property.propertyType) { case SerializedPropertyType.Float: minV = property.floatValue; maxV = pMaxV.floatValue; #if UNITY_PRE_5_5 EditorGUI.MinMaxSlider(label, position, ref minV, ref maxV, lBound, uBound); #else EditorGUI.MinMaxSlider(position, label, ref minV, ref maxV, lBound, uBound); #endif break; case SerializedPropertyType.Integer: minV = property.intValue; maxV = pMaxV.intValue; #if UNITY_PRE_5_5 EditorGUI.MinMaxSlider(label, position, ref minV, ref maxV, lBound, uBound); #else EditorGUI.MinMaxSlider(position, label, ref minV, ref maxV, lBound, uBound); #endif break; } if (EditorGUI.EndChangeCheck()) { // Clamp switch (property.propertyType) { case SerializedPropertyType.Float: if (DT._UseSnapValuePrecision && Attribute.Precision > -1) property.floatValue = Mathf.Max(lBound, DTMath.SnapPrecision(minV, Attribute.Precision)); pMaxV.floatValue = Mathf.Min(uBound, DTMath.SnapPrecision(maxV, Attribute.Precision)); break; case SerializedPropertyType.Integer: property.intValue = (int)minV; pMaxV.intValue = (int)maxV; break; } } label.tooltip = ""; } } [CustomPropertyDrawer(typeof(MinAttribute), true)] public class MinPropertyDrawer : DTPropertyDrawer { protected float minV; public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { base.OnGUI(position, property, label); // GUI EditorGUI.PropertyField(position, property, label); minV = Attribute.MinValue; if (!string.IsNullOrEmpty(Attribute.MinFieldOrPropertyName)) { minV = GetMemberValue(property, Attribute.MinFieldOrPropertyName); } // Clamp switch (property.propertyType) { case SerializedPropertyType.Float: if (DT._UseSnapValuePrecision && Attribute.Precision > -1) property.floatValue = DTMath.SnapPrecision(property.floatValue, Attribute.Precision); if (property.floatValue < minV) property.floatValue = minV; break; case SerializedPropertyType.Integer: if (property.intValue < minV) property.intValue = (int)minV; break; } label.tooltip = ""; } } [CustomPropertyDrawer(typeof(MaxAttribute))] public class MaxPropertyDrawer : DTPropertyDrawer { protected float maxV; public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { base.OnGUI(position, property, label); // GUI EditorGUI.PropertyField(position, property, label); maxV = Attribute.MaxValue; if (!string.IsNullOrEmpty(Attribute.MaxFieldOrPropertyName)) { maxV = GetMemberValue(property, Attribute.MaxFieldOrPropertyName); } // Clamp and snap switch (property.propertyType) { case SerializedPropertyType.Float: if (DT._UseSnapValuePrecision && Attribute.Precision > -1) property.floatValue = DTMath.SnapPrecision(property.floatValue, Attribute.Precision); if (property.floatValue > maxV) property.floatValue = maxV; break; case SerializedPropertyType.Integer: if (property.intValue > maxV) property.intValue = (int)maxV; break; } label.tooltip = ""; } } [CustomPropertyDrawer(typeof(PathSelectorAttribute))] public class PathSelectorDrawer : DTPropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { base.OnGUI(position, property, label); if (property.propertyType == SerializedPropertyType.String) { position.width -= 20; property.stringValue = EditorGUI.TextField(position, label, property.stringValue); position.xMin = position.xMax; position.width = 20; if (GUI.Button(position, "..", EditorStyles.miniButton)) { string v = string.Empty; switch (Attribute.Mode) { case PathSelectorAttribute.DialogMode.OpenFile: v = EditorUtility.OpenFilePanel(Attribute.Title, Attribute.Directory, Attribute.Extension); break; case PathSelectorAttribute.DialogMode.OpenFolder: v = EditorUtility.OpenFolderPanel(Attribute.Title, Attribute.Directory, Attribute.DefaultName); break; case PathSelectorAttribute.DialogMode.CreateFile: v = EditorUtility.SaveFilePanel(Attribute.Title, Attribute.Directory, Attribute.DefaultName, Attribute.Extension); break; } if (!string.IsNullOrEmpty(v)) property.stringValue = v; } } else DTLog.LogError("[DevTools] PropertyAttribute [FileSelector] only works on string fields!"); } } [CustomPropertyDrawer(typeof(EnumFlagAttribute))] public class EnumFlagDrawer : DTPropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { System.Enum targetEnum = GetPropertySourceField(property); EditorGUI.BeginProperty(position, label, property); #if UNITY_2017_3_OR_NEWER System.Enum enumNew = EditorGUI.EnumFlagsField(position, label, targetEnum); #else System.Enum enumNew = EditorGUI.EnumMaskField(position, label, targetEnum); #endif property.intValue = (int)System.Convert.ChangeType(enumNew, targetEnum.GetType(), System.Globalization.CultureInfo.InvariantCulture); EditorGUI.EndProperty(); } } [CustomPropertyDrawer(typeof(EnumSelectionGridAttribute))] public class EnumSelectionGridDrawer : DTPropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { if (property.propertyType == SerializedPropertyType.Enum) { string[] names = property.enumDisplayNames; EditorGUI.PrefixLabel(position, label); position = position.WithoutLabel(); EditorGUI.BeginChangeCheck(); int v = GUI.SelectionGrid(position, property.enumValueIndex, names, names.Length); if (EditorGUI.EndChangeCheck()) property.enumValueIndex = v; } else DTLog.LogError("[DevTools] PropertyAttribute [SelectionGrid] only works on Enum fields!"); } } [CustomPropertyDrawer(typeof(ObjectSelectorAttribute))] public class ObjectSelectorDrawer : DTPropertyDrawer { } [CustomPropertyDrawer(typeof(FloatRegionAttribute))] public class FloatRegionPropertyDrawer : DTPropertyDrawer { PropertyInfo mPI; object mObject; SerializedProperty ppFrom; SerializedProperty ppTo; SerializedProperty ppSimpleValue; RegionOptions mOptions; bool compact { get { return ((EditorGUIUtility.wideMode && Options == AttributeOptionsFlags.None) || ((Options & AttributeOptionsFlags.Compact) == AttributeOptionsFlags.Compact || (Options & AttributeOptionsFlags.FullCompact) == AttributeOptionsFlags.FullCompact ) ); } } bool minmax { get { return (Attribute.UseSlider && !ppSimpleValue.boolValue && mOptions.ClampFrom == DTValueClamping.Range && mOptions.ClampTo == DTValueClamping.Range && mOptions.FromMin == mOptions.ToMin && mOptions.FromMax == mOptions.ToMax); } } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { Init(property); float h = base.GetPropertyHeight(property, label); if (!compact) { if (!Attribute.RegionIsOptional || !ppSimpleValue.boolValue || minmax) h = (h + EditorGUIUtility.standardVerticalSpacing) * 2; } return h; } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { base.OnGUI(position, property, label); Init(property); Rect r = new Rect(position); r.height = EditorGUIUtility.singleLineHeight; if (minmax) { doMinmax(r, label, Attribute.RegionIsOptional); } else if (compact) { doCompact(r, label, new GUIContent(mOptions.LabelTo), Attribute.RegionIsOptional); } else { doRegular(r, label); } label.tooltip = ""; } void doMinmax(Rect r, GUIContent label, bool showOptional) { float l = ppFrom.floatValue; float u = ppTo.floatValue; Rect r2 = new Rect(r); if (showOptional) r2.width -= 16; EditorGUI.BeginChangeCheck(); #if UNITY_PRE_5_5 EditorGUI.MinMaxSlider(label, r2, ref l, ref u, mOptions.FromMin, mOptions.FromMax); #else EditorGUI.MinMaxSlider(r2, label, ref l, ref u, mOptions.FromMin, mOptions.FromMax); #endif if (EditorGUI.EndChangeCheck()) { ppFrom.floatValue = l; validateFrom(); ppTo.floatValue = u; validateTo(); } if (showOptional) { ppSimpleValue.boolValue = !EditorGUI.Toggle(new Rect(r.xMax - 16, r.y, 16, r.height), new GUIContent("", mOptions.OptionalTooltip), !ppSimpleValue.boolValue); } if (!compact) { r.y += r.height + EditorGUIUtility.standardVerticalSpacing; doCompact(r, new GUIContent(mOptions.LabelFrom), new GUIContent(mOptions.LabelTo), false); } } void doCompact(Rect r, GUIContent label, GUIContent labelTo, bool showOptional, bool fullLine = false) { float lw = EditorGUIUtility.labelWidth; float fw = EditorGUIUtility.fieldWidth; Rect r2 = new Rect(r); bool showTo = !showOptional || !ppSimpleValue.boolValue; float showToLabelWidth = DTGUI.LabelWidth(labelTo); float chkWidth = showOptional ? 16 : 0; if (fullLine) { EditorGUIUtility.labelWidth = DTGUI.LabelWidth(label); lw = EditorGUIUtility.labelWidth; } r2.width = (r.width - lw - chkWidth - showToLabelWidth) / 2; // From r.width = lw + r2.width; EditorGUI.BeginChangeCheck(); EditorGUI.PropertyField(r, ppFrom, label); if (EditorGUI.EndChangeCheck()) validateFrom(); r.x += r.width; // "To" may be optional if (showOptional) { ppSimpleValue.boolValue = !EditorGUI.Toggle(new Rect(r.x, r.y, 16, r.height), new GUIContent("", mOptions.OptionalTooltip), !ppSimpleValue.boolValue); r.x += 16; } // Show "To"? if (showTo) { EditorGUI.BeginChangeCheck(); EditorGUIUtility.labelWidth = showToLabelWidth; r.width = r2.width + showToLabelWidth; ppTo.floatValue = EditorGUI.FloatField(r, labelTo, ppTo.floatValue); if (EditorGUI.EndChangeCheck()) validateTo(); } EditorGUIUtility.labelWidth = lw; EditorGUIUtility.fieldWidth = fw; } void doRegular(Rect r, GUIContent label) { // From Rect rF = new Rect(r); if (Attribute.RegionIsOptional) rF.width -= 16; EditorGUI.BeginProperty(rF, label, ppFrom); EditorGUI.BeginChangeCheck(); if (Attribute.UseSlider && mOptions.ClampFrom == DTValueClamping.Range) ppFrom.floatValue = EditorGUI.Slider(rF, label, ppFrom.floatValue, mOptions.FromMin, mOptions.FromMax); else ppFrom.floatValue = EditorGUI.FloatField(rF, label, ppFrom.floatValue); if (EditorGUI.EndChangeCheck()) validateFrom(); EditorGUI.EndProperty(); // Perhaps To is optional if (Attribute.RegionIsOptional) ppSimpleValue.boolValue = !EditorGUI.Toggle(new Rect(rF.x + rF.width + 1, rF.y, 16, rF.height), new GUIContent("", mOptions.OptionalTooltip), !ppSimpleValue.boolValue); // To if (!Attribute.RegionIsOptional || !ppSimpleValue.boolValue) { r.y += r.height + EditorGUIUtility.standardVerticalSpacing; label.text = mOptions.LabelTo; EditorGUI.BeginProperty(r, label, ppTo); EditorGUI.BeginChangeCheck(); if (Attribute.UseSlider && mOptions.ClampTo == DTValueClamping.Range) ppTo.floatValue = EditorGUI.Slider(r, label, ppTo.floatValue, mOptions.ToMin, mOptions.ToMax); else ppTo.floatValue = EditorGUI.FloatField(r, label, ppTo.floatValue); if (EditorGUI.EndChangeCheck()) validateTo(); EditorGUI.EndProperty(); } } void validateFrom() { ppFrom.floatValue = DTMath.SnapPrecision(ppFrom.floatValue, Attribute.Precision); switch (mOptions.ClampFrom) { case DTValueClamping.Min: ppFrom.floatValue = Mathf.Max(mOptions.FromMin, ppFrom.floatValue); break; case DTValueClamping.Max: ppFrom.floatValue = Mathf.Min(mOptions.FromMax, ppFrom.floatValue); break; case DTValueClamping.Range: ppFrom.floatValue = Mathf.Clamp(ppFrom.floatValue, mOptions.FromMin, mOptions.FromMax); if (minmax) ppFrom.floatValue = Mathf.Min(ppFrom.floatValue, ppTo.floatValue); break; } } void validateTo() { ppTo.floatValue = DTMath.SnapPrecision(ppTo.floatValue, Attribute.Precision); switch (mOptions.ClampTo) { case DTValueClamping.Min: ppTo.floatValue = Mathf.Max(mOptions.ToMin, ppTo.floatValue); break; case DTValueClamping.Max: ppTo.floatValue = Mathf.Min(mOptions.ToMax, ppTo.floatValue); break; case DTValueClamping.Range: ppTo.floatValue = Mathf.Clamp(ppTo.floatValue, mOptions.ToMin, mOptions.ToMax); if (minmax) ppTo.floatValue = Mathf.Max(ppTo.floatValue, ppFrom.floatValue); break; } } void Init(SerializedProperty property) { try { mObject = property.GetParentObject(); if (mPI == null && !string.IsNullOrEmpty(Attribute.RegionOptionsPropertyName)) mPI = mObject.GetType().PropertyByName(Attribute.RegionOptionsPropertyName, true, true); if (mPI != null) mOptions = (RegionOptions)mPI.GetValue(mObject, null); else mOptions = RegionOptions.Default; } catch (System.Exception e) { DTLog.LogError("[DevTools] FloatRegionPropertyDrawer: Unable to find property '" + Attribute.RegionOptionsPropertyName + "'! (" + e.ToString() + ")"); } ppFrom = property.FindPropertyRelative("From"); ppTo = property.FindPropertyRelative("To"); ppSimpleValue = property.FindPropertyRelative("SimpleValue"); if (minmax) ppSimpleValue.boolValue = false; } } [CustomPropertyDrawer(typeof(IntRegionAttribute))] public class IntRegionPropertyDrawer : DTPropertyDrawer { PropertyInfo mPI; object mObject; SerializedProperty ppFrom; SerializedProperty ppTo; SerializedProperty ppSimpleValue; RegionOptions mOptions; bool compact { get { return (EditorGUIUtility.wideMode || ((Options & AttributeOptionsFlags.Compact) == AttributeOptionsFlags.Compact || (Options & AttributeOptionsFlags.FullCompact) == AttributeOptionsFlags.FullCompact ) ); } } bool minmax { get { return (Attribute.UseSlider && !Attribute.RegionIsOptional && mOptions.ClampFrom == DTValueClamping.Range && mOptions.ClampTo == DTValueClamping.Range && mOptions.FromMin == mOptions.ToMin && mOptions.FromMax == mOptions.ToMax); } } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { Init(property); float h = base.GetPropertyHeight(property, label); if (!compact) { if (!Attribute.RegionIsOptional || !ppSimpleValue.boolValue || minmax) h = (h + EditorGUIUtility.standardVerticalSpacing) * 2; } return h; } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { base.OnGUI(position, property, label); Init(property); Rect r = new Rect(position); r.height = EditorGUIUtility.singleLineHeight; if (minmax) { doMinmax(r, label, Attribute.RegionIsOptional); } else if (compact) { doCompact(r, label, new GUIContent(mOptions.LabelTo), Attribute.RegionIsOptional); } else { doRegular(r, label); } label.tooltip = ""; } void doMinmax(Rect r, GUIContent label, bool showOptional) { float l = ppFrom.intValue; float u = ppTo.intValue; Rect r2 = new Rect(r); if (showOptional) r2.width -= 16; EditorGUI.BeginChangeCheck(); #if UNITY_PRE_5_5 EditorGUI.MinMaxSlider(label, r2, ref l, ref u, mOptions.FromMin, mOptions.FromMax); #else EditorGUI.MinMaxSlider(r2, label, ref l, ref u, mOptions.FromMin, mOptions.FromMax); #endif if (EditorGUI.EndChangeCheck()) { ppFrom.intValue = (int)l; validateFrom(); ppTo.intValue = (int)u; validateTo(); } if (showOptional) { ppSimpleValue.boolValue = !EditorGUI.Toggle(new Rect(r.xMax - 16, r.y, 16, r.height), new GUIContent("", mOptions.OptionalTooltip), !ppSimpleValue.boolValue); } if (!compact) { r.y += r.height + EditorGUIUtility.standardVerticalSpacing; doCompact(r, new GUIContent(mOptions.LabelFrom), new GUIContent(mOptions.LabelTo), false); } } void doCompact(Rect r, GUIContent label, GUIContent labelTo, bool showOptional, bool fullLine = false) { float lw = EditorGUIUtility.labelWidth; float fw = EditorGUIUtility.fieldWidth; Rect r2 = new Rect(r); bool showTo = !showOptional || !ppSimpleValue.boolValue; float showToLabelWidth = DTGUI.LabelWidth(labelTo); float chkWidth = showOptional ? 16 : 0; if (fullLine) { EditorGUIUtility.labelWidth = DTGUI.LabelWidth(label); lw = EditorGUIUtility.labelWidth; } r2.width = (r.width - lw - chkWidth - showToLabelWidth) / 2; // From r.width = lw + r2.width; EditorGUI.BeginChangeCheck(); EditorGUI.PropertyField(r, ppFrom, label); if (EditorGUI.EndChangeCheck()) validateFrom(); r.x += r.width; // "To" may be optional if (showOptional) { ppSimpleValue.boolValue = !EditorGUI.Toggle(new Rect(r.x, r.y, 16, r.height), new GUIContent("", mOptions.OptionalTooltip), !ppSimpleValue.boolValue); r.x += 16; } // Show "To"? if (showTo) { EditorGUI.BeginChangeCheck(); EditorGUIUtility.labelWidth = showToLabelWidth; r.width = r2.width + showToLabelWidth; ppTo.intValue = EditorGUI.IntField(r, labelTo, ppTo.intValue); if (EditorGUI.EndChangeCheck()) validateTo(); } EditorGUIUtility.labelWidth = lw; EditorGUIUtility.fieldWidth = fw; } void doRegular(Rect r, GUIContent label) { // From Rect rF = new Rect(r); if (Attribute.RegionIsOptional) rF.width -= 16; EditorGUI.BeginProperty(rF, label, ppFrom); EditorGUI.BeginChangeCheck(); if (Attribute.UseSlider && mOptions.ClampFrom == DTValueClamping.Range) ppFrom.intValue = EditorGUI.IntSlider(rF, label, ppFrom.intValue, mOptions.FromMin, mOptions.FromMax); else ppFrom.intValue = EditorGUI.IntField(rF, label, ppFrom.intValue); if (EditorGUI.EndChangeCheck()) validateFrom(); EditorGUI.EndProperty(); // Perhaps To is optional if (Attribute.RegionIsOptional) ppSimpleValue.boolValue = !EditorGUI.Toggle(new Rect(rF.x + rF.width, rF.y, 16, rF.height), new GUIContent("", mOptions.OptionalTooltip), !ppSimpleValue.boolValue); // To if (!Attribute.RegionIsOptional || !ppSimpleValue.boolValue) { r.y += r.height + EditorGUIUtility.standardVerticalSpacing; label.text = mOptions.LabelTo; EditorGUI.BeginProperty(r, label, ppTo); EditorGUI.BeginChangeCheck(); if (Attribute.UseSlider && mOptions.ClampTo == DTValueClamping.Range) ppTo.intValue = EditorGUI.IntSlider(r, label, ppTo.intValue, mOptions.ToMin, mOptions.ToMax); else ppTo.intValue = EditorGUI.IntField(r, label, ppTo.intValue); if (EditorGUI.EndChangeCheck()) validateTo(); EditorGUI.EndProperty(); } } void validateFrom() { switch (mOptions.ClampFrom) { case DTValueClamping.Min: ppFrom.intValue = Mathf.Max(mOptions.FromMin, ppFrom.intValue); break; case DTValueClamping.Max: ppFrom.intValue = Mathf.Min(mOptions.FromMax, ppFrom.intValue); break; case DTValueClamping.Range: ppFrom.intValue = Mathf.Clamp(ppFrom.intValue, mOptions.FromMin, mOptions.FromMax); if (minmax) ppFrom.intValue = Mathf.Min(ppFrom.intValue, ppTo.intValue); break; } } void validateTo() { switch (mOptions.ClampTo) { case DTValueClamping.Min: ppTo.intValue = Mathf.Max(mOptions.ToMin, ppTo.intValue); break; case DTValueClamping.Max: ppTo.intValue = Mathf.Min(mOptions.ToMax, ppTo.intValue); break; case DTValueClamping.Range: ppTo.intValue = Mathf.Clamp(ppTo.intValue, mOptions.ToMin, mOptions.ToMax); if (minmax) ppTo.intValue = Mathf.Max(ppTo.intValue, ppFrom.intValue); break; } } void Init(SerializedProperty property) { try { mObject = property.GetParentObject(); if (mPI == null && !string.IsNullOrEmpty(Attribute.RegionOptionsPropertyName)) mPI = mObject.GetType().PropertyByName(Attribute.RegionOptionsPropertyName, true, true); if (mPI != null) mOptions = (RegionOptions)mPI.GetValue(mObject, null); else mOptions = RegionOptions.Default; } catch (System.Exception e) { DTLog.LogError("[DevTools] IntRegionPropertyDrawer: Unable to find property '" + Attribute.RegionOptionsPropertyName + "'! (" + e.ToString() + ")"); } ppFrom = property.FindPropertyRelative("From"); ppTo = property.FindPropertyRelative("To"); ppSimpleValue = property.FindPropertyRelative("SimpleValue"); } } }