HelpAttribute.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. // --------------------------------------------------------------------------------------------------------------------
  2. /// <copyright file="HelpAttribute.cs">
  3. /// <See cref="https://github.com/johnearnshaw/unity-inspector-help"></See>
  4. /// Copyright (c) 2017, John Earnshaw, reblGreen Software Limited
  5. /// <See cref="https://github.com/johnearnshaw/"></See>
  6. /// <See cref="https://bitbucket.com/juanshaf/"></See>
  7. /// <See cref="https://reblgreen.com/"></See>
  8. /// All rights reserved.
  9. /// Redistribution and use in source and binary forms, with or without modification, are
  10. /// permitted provided that the following conditions are met:
  11. /// 1. Redistributions of source code must retain the above copyright notice, this list of
  12. /// conditions and the following disclaimer.
  13. /// 2. Redistributions in binary form must reproduce the above copyright notice, this list
  14. /// of conditions and the following disclaimer in the documentation and/or other materials
  15. /// provided with the distribution.
  16. /// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
  17. /// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  18. /// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE
  19. /// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  20. /// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  21. /// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  22. /// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  23. /// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  24. /// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. /// </copyright>
  26. // --------------------------------------------------------------------------------------------------------------------
  27. using System;
  28. using UnityEngine;
  29. #if UNITY_EDITOR
  30. using UnityEditor;
  31. #endif
  32. [AttributeUsage(AttributeTargets.Field, Inherited = true)]
  33. public class HelpAttribute : PropertyAttribute
  34. {
  35. public readonly string text;
  36. // MessageType exists in UnityEditor namespace and can throw an exception when used outside the editor.
  37. // We spoof MessageType at the bottom of this script to ensure that errors are not thrown when
  38. // MessageType is unavailable.
  39. public readonly MessageType type;
  40. /// <summary>
  41. /// Adds a HelpBox to the Unity property inspector above this field.
  42. /// </summary>
  43. /// <param name="text">The help text to be displayed in the HelpBox.</param>
  44. /// <param name="type">The icon to be displayed in the HelpBox.</param>
  45. public HelpAttribute(string text, MessageType type = MessageType.Info)
  46. {
  47. this.text = text;
  48. this.type = type;
  49. }
  50. }
  51. #if UNITY_EDITOR
  52. [CustomPropertyDrawer(typeof(HelpAttribute))]
  53. public class HelpDrawer : PropertyDrawer
  54. {
  55. // Used for top and bottom padding between the text and the HelpBox border.
  56. const int paddingHeight = 8;
  57. // Used to add some margin between the the HelpBox and the property.
  58. const int marginHeight = 2;
  59. // Global field to store the original (base) property height.
  60. float baseHeight = 0;
  61. // Custom added height for drawing text area which has the MultilineAttribute.
  62. float addedHeight = 0;
  63. /// <summary>
  64. /// A wrapper which returns the PropertyDrawer.attribute field as a HelpAttribute.
  65. /// </summary>
  66. HelpAttribute helpAttribute { get { return (HelpAttribute)attribute; } }
  67. /// <summary>
  68. /// A helper property to check for RangeAttribute.
  69. /// </summary>
  70. RangeAttribute rangeAttribute
  71. {
  72. get
  73. {
  74. var attributes = fieldInfo.GetCustomAttributes(typeof(RangeAttribute), true);
  75. return attributes != null && attributes.Length > 0 ? (RangeAttribute)attributes[0] : null;
  76. }
  77. }
  78. /// <summary>
  79. /// A helper property to check for MultiLineAttribute.
  80. /// </summary>
  81. MultilineAttribute multilineAttribute
  82. {
  83. get
  84. {
  85. var attributes = fieldInfo.GetCustomAttributes(typeof(MultilineAttribute), true);
  86. return attributes != null && attributes.Length > 0 ? (MultilineAttribute)attributes[0] : null;
  87. }
  88. }
  89. public override float GetPropertyHeight(SerializedProperty prop, GUIContent label)
  90. {
  91. // We store the original property height for later use...
  92. baseHeight = base.GetPropertyHeight(prop, label);
  93. // This stops icon shrinking if text content doesn't fill out the container enough.
  94. float minHeight = paddingHeight * 5;
  95. // Calculate the height of the HelpBox using the GUIStyle on the current skin and the inspector
  96. // window's currentViewWidth.
  97. var content = new GUIContent(helpAttribute.text);
  98. var style = GUI.skin.GetStyle("helpbox");
  99. var height = style.CalcHeight(content, EditorGUIUtility.currentViewWidth);
  100. // We add tiny padding here to make sure the text is not overflowing the HelpBox from the top
  101. // and bottom.
  102. height += marginHeight * 2;
  103. // Since we draw a custom text area with the label above if our property contains the
  104. // MultilineAttribute, we need to add some extra height to compensate. This is stored in a
  105. // seperate global field so we can use it again later.
  106. if (multilineAttribute != null && prop.propertyType == SerializedPropertyType.String)
  107. {
  108. addedHeight = 48f;
  109. }
  110. // If the calculated HelpBox is less than our minimum height we use this to calculate the returned
  111. // height instead.
  112. return height > minHeight ? height + baseHeight + addedHeight : minHeight + baseHeight + addedHeight;
  113. }
  114. public override void OnGUI(Rect position, SerializedProperty prop, GUIContent label)
  115. {
  116. // We get a local reference to the MultilineAttribute as we use it twice in this method and it
  117. // saves calling the logic twice for minimal optimization, etc...
  118. var multiline = multilineAttribute;
  119. EditorGUI.BeginProperty(position, label, prop);
  120. // Copy the position out so we can calculate the position of our HelpBox without affecting the
  121. // original position.
  122. var helpPos = position;
  123. helpPos.height -= baseHeight + marginHeight;
  124. if (multiline != null)
  125. {
  126. helpPos.height -= addedHeight;
  127. }
  128. // Renders the HelpBox in the Unity inspector UI.
  129. EditorGUI.HelpBox(helpPos, helpAttribute.text, helpAttribute.type);
  130. position.y += helpPos.height + marginHeight;
  131. position.height = baseHeight;
  132. // If we have a RangeAttribute on our field, we need to handle the PropertyDrawer differently to
  133. // keep the same style as Unity's default.
  134. var range = rangeAttribute;
  135. if (range != null)
  136. {
  137. if (prop.propertyType == SerializedPropertyType.Float)
  138. {
  139. EditorGUI.Slider(position, prop, range.min, range.max, label);
  140. }
  141. else if (prop.propertyType == SerializedPropertyType.Integer)
  142. {
  143. EditorGUI.IntSlider(position, prop, (int)range.min, (int)range.max, label);
  144. }
  145. else
  146. {
  147. // Not numeric so draw standard property field as punishment for adding RangeAttribute to
  148. // a property which can not have a range :P
  149. EditorGUI.PropertyField(position, prop, label);
  150. }
  151. }
  152. else if (multiline != null)
  153. {
  154. // Here's where we handle the PropertyDrawer differently if we have a MultiLineAttribute, to try
  155. // and keep some kind of multiline text area. This is not identical to Unity's default but is
  156. // better than nothing...
  157. if (prop.propertyType == SerializedPropertyType.String)
  158. {
  159. var style = GUI.skin.label;
  160. var size = style.CalcHeight(label, EditorGUIUtility.currentViewWidth);
  161. EditorGUI.LabelField(position, label);
  162. position.y += size;
  163. position.height += addedHeight - size;
  164. // Fixed text dissappearing thanks to: http://answers.unity3d.com/questions/244043/textarea-does-not-work-text-dissapears-solution-is.html
  165. prop.stringValue = EditorGUI.TextArea(position, prop.stringValue);
  166. }
  167. else
  168. {
  169. // Again with a MultilineAttribute on a non-text field deserves for the standard property field
  170. // to be drawn as punishment :P
  171. EditorGUI.PropertyField(position, prop, label);
  172. }
  173. }
  174. else
  175. {
  176. // If we get to here it means we're drawing the default property field below the HelpBox. More custom
  177. // and built in PropertyDrawers could be implemented to enable HelpBox but it could easily make for
  178. // hefty else/if block which would need refactoring!
  179. EditorGUI.PropertyField(position, prop, label);
  180. }
  181. EditorGUI.EndProperty();
  182. }
  183. }
  184. #else
  185. // Replicate MessageType Enum if we are not in editor as this enum exists in UnityEditor namespace.
  186. // This should stop errors being logged the same as Shawn Featherly's commit in the Github repo but I
  187. // feel is cleaner than having the conditional directive in the middle of the HelpAttribute constructor.
  188. public enum MessageType
  189. {
  190. None,
  191. Info,
  192. Warning,
  193. Error,
  194. }
  195. #endif