1+ using System ;
2+ using UnityEngine ;
3+ #if UNITY_EDITOR
4+ using UnityEditor ;
5+ #endif
6+
7+ [ AttributeUsage ( AttributeTargets . Field , Inherited = true ) ]
8+ public class HelpAttribute : PropertyAttribute
9+ {
10+ public readonly string text ;
11+
12+ // MessageType exists in UnityEditor namespace and can throw an exception when used outside the editor.
13+ // We spoof MessageType at the bottom of this script to ensure that errors are not thrown when
14+ // MessageType is unavailable.
15+ public readonly MessageType type ;
16+
17+
18+ /// <summary>
19+ /// Adds a HelpBox to the Unity property inspector above this field.
20+ /// </summary>
21+ /// <param name="text">The help text to be displayed in the HelpBox.</param>
22+ /// <param name="type">The icon to be displayed in the HelpBox.</param>
23+ public HelpAttribute ( string text , MessageType type = MessageType . Info )
24+ {
25+ this . text = text ;
26+ this . type = type ;
27+ }
28+ }
29+
30+ #if UNITY_EDITOR
31+ [ CustomPropertyDrawer ( typeof ( HelpAttribute ) ) ]
32+ public class HelpDrawer : PropertyDrawer
33+ {
34+ // Used for top and bottom padding between the text and the HelpBox border.
35+ const int paddingHeight = 8 ;
36+
37+ // Used to add some margin between the the HelpBox and the property.
38+ const int marginHeight = 2 ;
39+
40+ // Global field to store the original (base) property height.
41+ float baseHeight = 0 ;
42+
43+ // Custom added height for drawing text area which has the MultilineAttribute.
44+ float addedHeight = 0 ;
45+
46+ /// <summary>
47+ /// A wrapper which returns the PropertyDrawer.attribute field as a HelpAttribute.
48+ /// </summary>
49+ HelpAttribute helpAttribute { get { return ( HelpAttribute ) attribute ; } }
50+
51+ /// <summary>
52+ /// A helper property to check for RangeAttribute.
53+ /// </summary>
54+ RangeAttribute rangeAttribute
55+ {
56+ get
57+ {
58+ var attributes = fieldInfo . GetCustomAttributes ( typeof ( RangeAttribute ) , true ) ;
59+ return attributes != null && attributes . Length > 0 ? ( RangeAttribute ) attributes [ 0 ] : null ;
60+ }
61+ }
62+
63+ /// <summary>
64+ /// A helper property to check for MultiLineAttribute.
65+ /// </summary>
66+ MultilineAttribute multilineAttribute
67+ {
68+ get
69+ {
70+ var attributes = fieldInfo . GetCustomAttributes ( typeof ( MultilineAttribute ) , true ) ;
71+ return attributes != null && attributes . Length > 0 ? ( MultilineAttribute ) attributes [ 0 ] : null ;
72+ }
73+ }
74+
75+
76+ public override float GetPropertyHeight ( SerializedProperty prop , GUIContent label )
77+ {
78+ // We store the original property height for later use...
79+ baseHeight = base . GetPropertyHeight ( prop , label ) ;
80+
81+ // This stops icon shrinking if text content doesn't fill out the container enough.
82+ float minHeight = paddingHeight * 5 ;
83+
84+ // Calculate the height of the HelpBox using the GUIStyle on the current skin and the inspector
85+ // window's currentViewWidth.
86+ var content = new GUIContent ( helpAttribute . text ) ;
87+ var style = GUI . skin . GetStyle ( "helpbox" ) ;
88+
89+ var height = style . CalcHeight ( content , EditorGUIUtility . currentViewWidth ) ;
90+
91+ // We add tiny padding here to make sure the text is not overflowing the HelpBox from the top
92+ // and bottom.
93+ height += marginHeight * 2 ;
94+
95+ // Since we draw a custom text area with the label above if our property contains the
96+ // MultilineAttribute, we need to add some extra height to compensate. This is stored in a
97+ // seperate global field so we can use it again later.
98+ if ( multilineAttribute != null && prop . propertyType == SerializedPropertyType . String )
99+ {
100+ addedHeight = 48f ;
101+ }
102+
103+ // If the calculated HelpBox is less than our minimum height we use this to calculate the returned
104+ // height instead.
105+ return height > minHeight ? height + baseHeight + addedHeight : minHeight + baseHeight + addedHeight ;
106+ }
107+
108+
109+ public override void OnGUI ( Rect position , SerializedProperty prop , GUIContent label )
110+ {
111+ // We get a local reference to the MultilineAttribute as we use it twice in this method and it
112+ // saves calling the logic twice for minimal optimization, etc...
113+ var multiline = multilineAttribute ;
114+
115+ EditorGUI . BeginProperty ( position , label , prop ) ;
116+
117+ // Copy the position out so we can calculate the position of our HelpBox without affecting the
118+ // original position.
119+ var helpPos = position ;
120+
121+ helpPos . height -= baseHeight + marginHeight ;
122+
123+
124+ if ( multiline != null )
125+ {
126+ helpPos . height -= addedHeight ;
127+ }
128+
129+ // Renders the HelpBox in the Unity inspector UI.
130+ EditorGUI . HelpBox ( helpPos , helpAttribute . text , helpAttribute . type ) ;
131+
132+ position . y += helpPos . height + marginHeight ;
133+ position . height = baseHeight ;
134+
135+
136+ // If we have a RangeAttribute on our field, we need to handle the PropertyDrawer differently to
137+ // keep the same style as Unity's default.
138+ var range = rangeAttribute ;
139+
140+ if ( range != null )
141+ {
142+ if ( prop . propertyType == SerializedPropertyType . Float )
143+ {
144+ EditorGUI . Slider ( position , prop , range . min , range . max , label ) ;
145+ }
146+ else if ( prop . propertyType == SerializedPropertyType . Integer )
147+ {
148+ EditorGUI . IntSlider ( position , prop , ( int ) range . min , ( int ) range . max , label ) ;
149+ }
150+ else
151+ {
152+ // Not numeric so draw standard property field as punishment for adding RangeAttribute to
153+ // a property which can not have a range :P
154+ EditorGUI . PropertyField ( position , prop , label ) ;
155+ }
156+ }
157+ else if ( multiline != null )
158+ {
159+ // Here's where we handle the PropertyDrawer differently if we have a MultiLineAttribute, to try
160+ // and keep some kind of multiline text area. This is not identical to Unity's default but is
161+ // better than nothing...
162+ if ( prop . propertyType == SerializedPropertyType . String )
163+ {
164+ var style = GUI . skin . label ;
165+ var size = style . CalcHeight ( label , EditorGUIUtility . currentViewWidth ) ;
166+
167+ EditorGUI . LabelField ( position , label ) ;
168+
169+ position . y += size ;
170+ position . height += addedHeight - size ;
171+
172+ // Fixed text dissappearing thanks to: http://answers.unity3d.com/questions/244043/textarea-does-not-work-text-dissapears-solution-is.html
173+ prop . stringValue = EditorGUI . TextArea ( position , prop . stringValue ) ;
174+ }
175+ else
176+ {
177+ // Again with a MultilineAttribute on a non-text field deserves for the standard property field
178+ // to be drawn as punishment :P
179+ EditorGUI . PropertyField ( position , prop , label ) ;
180+ }
181+ }
182+ else
183+ {
184+ // If we get to here it means we're drawing the default property field below the HelpBox. More custom
185+ // and built in PropertyDrawers could be implemented to enable HelpBox but it could easily make for
186+ // hefty else/if block which would need refactoring!
187+ EditorGUI . PropertyField ( position , prop , label ) ;
188+ }
189+
190+ EditorGUI . EndProperty ( ) ;
191+ }
192+ }
193+ #else
194+ // Replicate MessageType Enum if we are not in editor as this enum exists in UnityEditor namespace.
195+ // This should stop errors being logged the same as Shawn Featherly's commit in the Github repo but I
196+ // feel is cleaner than having the conditional directive in the middle of the HelpAttribute constructor.
197+ public enum MessageType
198+ {
199+ None ,
200+ Info ,
201+ Warning ,
202+ Error ,
203+ }
204+ #endif
0 commit comments