// -------------------------------------------------------------------------------------------------------------------- /// /// /// Copyright (c) 2017, John Earnshaw, reblGreen Software Limited /// /// /// /// All rights reserved. /// Redistribution and use in source and binary forms, with or without modification, are /// permitted provided that the following conditions are met: /// 1. Redistributions of source code must retain the above copyright notice, this list of /// conditions and the following disclaimer. /// 2. Redistributions in binary form must reproduce the above copyright notice, this list /// of conditions and the following disclaimer in the documentation and/or other materials /// provided with the distribution. /// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY /// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF /// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE /// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, /// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF /// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) /// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR /// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS /// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// // -------------------------------------------------------------------------------------------------------------------- using System; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif [AttributeUsage(AttributeTargets.Field, Inherited = true)] public class HelpAttribute : PropertyAttribute { public readonly string text; // MessageType exists in UnityEditor namespace and can throw an exception when used outside the editor. // We spoof MessageType at the bottom of this script to ensure that errors are not thrown when // MessageType is unavailable. public readonly MessageType type; /// /// Adds a HelpBox to the Unity property inspector above this field. /// /// The help text to be displayed in the HelpBox. /// The icon to be displayed in the HelpBox. public HelpAttribute(string text, MessageType type = MessageType.Info) { this.text = text; this.type = type; } } #if UNITY_EDITOR [CustomPropertyDrawer(typeof(HelpAttribute))] public class HelpDrawer : PropertyDrawer { // Used for top and bottom padding between the text and the HelpBox border. const int paddingHeight = 8; // Used to add some margin between the the HelpBox and the property. const int marginHeight = 2; // Global field to store the original (base) property height. float baseHeight = 0; // Custom added height for drawing text area which has the MultilineAttribute. float addedHeight = 0; /// /// A wrapper which returns the PropertyDrawer.attribute field as a HelpAttribute. /// HelpAttribute helpAttribute { get { return (HelpAttribute)attribute; } } /// /// A helper property to check for RangeAttribute. /// RangeAttribute rangeAttribute { get { var attributes = fieldInfo.GetCustomAttributes(typeof(RangeAttribute), true); return attributes != null && attributes.Length > 0 ? (RangeAttribute)attributes[0] : null; } } /// /// A helper property to check for MultiLineAttribute. /// MultilineAttribute multilineAttribute { get { var attributes = fieldInfo.GetCustomAttributes(typeof(MultilineAttribute), true); return attributes != null && attributes.Length > 0 ? (MultilineAttribute)attributes[0] : null; } } public override float GetPropertyHeight(SerializedProperty prop, GUIContent label) { // We store the original property height for later use... baseHeight = base.GetPropertyHeight(prop, label); // This stops icon shrinking if text content doesn't fill out the container enough. float minHeight = paddingHeight * 5; // Calculate the height of the HelpBox using the GUIStyle on the current skin and the inspector // window's currentViewWidth. var content = new GUIContent(helpAttribute.text); var style = GUI.skin.GetStyle("helpbox"); var height = style.CalcHeight(content, EditorGUIUtility.currentViewWidth); // We add tiny padding here to make sure the text is not overflowing the HelpBox from the top // and bottom. height += marginHeight * 2; // Since we draw a custom text area with the label above if our property contains the // MultilineAttribute, we need to add some extra height to compensate. This is stored in a // seperate global field so we can use it again later. if (multilineAttribute != null && prop.propertyType == SerializedPropertyType.String) { addedHeight = 48f; } // If the calculated HelpBox is less than our minimum height we use this to calculate the returned // height instead. return height > minHeight ? height + baseHeight + addedHeight : minHeight + baseHeight + addedHeight; } public override void OnGUI(Rect position, SerializedProperty prop, GUIContent label) { // We get a local reference to the MultilineAttribute as we use it twice in this method and it // saves calling the logic twice for minimal optimization, etc... var multiline = multilineAttribute; EditorGUI.BeginProperty(position, label, prop); // Copy the position out so we can calculate the position of our HelpBox without affecting the // original position. var helpPos = position; helpPos.height -= baseHeight + marginHeight; if (multiline != null) { helpPos.height -= addedHeight; } // Renders the HelpBox in the Unity inspector UI. EditorGUI.HelpBox(helpPos, helpAttribute.text, helpAttribute.type); position.y += helpPos.height + marginHeight; position.height = baseHeight; // If we have a RangeAttribute on our field, we need to handle the PropertyDrawer differently to // keep the same style as Unity's default. var range = rangeAttribute; if (range != null) { if (prop.propertyType == SerializedPropertyType.Float) { EditorGUI.Slider(position, prop, range.min, range.max, label); } else if (prop.propertyType == SerializedPropertyType.Integer) { EditorGUI.IntSlider(position, prop, (int)range.min, (int)range.max, label); } else { // Not numeric so draw standard property field as punishment for adding RangeAttribute to // a property which can not have a range :P EditorGUI.PropertyField(position, prop, label); } } else if (multiline != null) { // Here's where we handle the PropertyDrawer differently if we have a MultiLineAttribute, to try // and keep some kind of multiline text area. This is not identical to Unity's default but is // better than nothing... if (prop.propertyType == SerializedPropertyType.String) { var style = GUI.skin.label; var size = style.CalcHeight(label, EditorGUIUtility.currentViewWidth); EditorGUI.LabelField(position, label); position.y += size; position.height += addedHeight - size; // Fixed text dissappearing thanks to: http://answers.unity3d.com/questions/244043/textarea-does-not-work-text-dissapears-solution-is.html prop.stringValue = EditorGUI.TextArea(position, prop.stringValue); } else { // Again with a MultilineAttribute on a non-text field deserves for the standard property field // to be drawn as punishment :P EditorGUI.PropertyField(position, prop, label); } } else { // If we get to here it means we're drawing the default property field below the HelpBox. More custom // and built in PropertyDrawers could be implemented to enable HelpBox but it could easily make for // hefty else/if block which would need refactoring! EditorGUI.PropertyField(position, prop, label); } EditorGUI.EndProperty(); } } #else // Replicate MessageType Enum if we are not in editor as this enum exists in UnityEditor namespace. // This should stop errors being logged the same as Shawn Featherly's commit in the Github repo but I // feel is cleaner than having the conditional directive in the middle of the HelpAttribute constructor. public enum MessageType { None, Info, Warning, Error, } #endif