using System; using System.Linq; using System.Text; using UnityEngine; using UnityEditor; namespace FMODUnity { [CustomPropertyDrawer(typeof(EventReference))] public class EventReferenceDrawer : PropertyDrawer { private static readonly Texture RepairIcon = EditorUtils.LoadImage("Wrench.png"); private static readonly Texture WarningIcon = EditorUtils.LoadImage("NotFound.png"); private static readonly GUIContent NotFoundWarning = new GUIContent(L10n.Tr("Event Not Found"), WarningIcon); private static GUIStyle buttonStyle; private static Vector2 WarningSize() { return GUI.skin.label.CalcSize(NotFoundWarning); } private static float GetBaseHeight() { return GUI.skin.textField.CalcSize(GUIContent.none).y; } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { if (buttonStyle == null) { buttonStyle = new GUIStyle(GUI.skin.button); buttonStyle.padding.top = 1; buttonStyle.padding.bottom = 1; } Texture browseIcon = EditorUtils.LoadImage("SearchIconBlack.png"); Texture openIcon = EditorUtils.LoadImage("BrowserIcon.png"); Texture addIcon = EditorUtils.LoadImage("AddIcon.png"); Texture copyIcon = EditorUtils.LoadImage("CopyIcon.png"); using (new EditorGUI.PropertyScope(position, label, property)) { HandleDragEvents(position, property); EventReference eventReference = property.GetEventReference(); EditorEventRef editorEventRef = GetEditorEventRef(eventReference); float baseHeight = GetBaseHeight(); Rect headerRect = position; headerRect.width = EditorGUIUtility.labelWidth; headerRect.height = baseHeight; property.isExpanded = EditorGUI.Foldout(headerRect, property.isExpanded, label, true); Rect addRect = new Rect(position.xMax - addIcon.width - 7, position.y, addIcon.width + 7, baseHeight); Rect openRect = new Rect(addRect.x - openIcon.width - 7, position.y, openIcon.width + 6, baseHeight); Rect searchRect = new Rect(openRect.x - browseIcon.width - 9, position.y, browseIcon.width + 8, baseHeight); Rect pathRect = position; pathRect.xMin = headerRect.xMax; pathRect.xMax = searchRect.x - 3; pathRect.height = baseHeight; SerializedProperty pathProperty = GetPathProperty(property); using (var scope = new EditorGUI.ChangeCheckScope()) { EditorGUI.PropertyField(pathRect, pathProperty, GUIContent.none); if (scope.changed) { SetEvent(property, pathProperty.stringValue); } } if (GUI.Button(searchRect, new GUIContent(browseIcon, L10n.Tr("Search")), buttonStyle)) { var eventBrowser = ScriptableObject.CreateInstance(); eventBrowser.ChooseEvent(property); var windowRect = position; windowRect.xMin = pathRect.xMin; windowRect.position = GUIUtility.GUIToScreenPoint(windowRect.position); windowRect.height = openRect.height + 1; windowRect.width = Mathf.Max(windowRect.width, 300f); eventBrowser.ShowAsDropDown(windowRect, new Vector2(windowRect.width, 400)); } if (GUI.Button(addRect, new GUIContent(addIcon, L10n.Tr("Create New Event in Studio")), buttonStyle)) { var addDropdown = EditorWindow.CreateInstance(); addDropdown.SelectEvent(property); var windowRect = position; windowRect.xMin = pathRect.xMin; windowRect.position = GUIUtility.GUIToScreenPoint(windowRect.position); windowRect.height = openRect.height + 1; windowRect.width = Mathf.Max(windowRect.width, 300f); addDropdown.ShowAsDropDown(windowRect, new Vector2(windowRect.width, 500)); } if (GUI.Button(openRect, new GUIContent(openIcon, L10n.Tr("Open In Browser")), buttonStyle)) { EventBrowser.ShowWindow(); EventBrowser eventBrowser = EditorWindow.GetWindow(); eventBrowser.FrameEvent(pathProperty.stringValue); } if (editorEventRef != null) { float labelY = headerRect.y + baseHeight; MismatchInfo mismatch = GetMismatch(eventReference, editorEventRef); if (mismatch != null) { Rect warningRect = pathRect; warningRect.xMax = position.xMax; warningRect.y = labelY; warningRect.height = WarningSize().y; DrawMismatchUI(warningRect, openRect.x, openRect.width, mismatch, property); labelY = warningRect.yMax; } if (property.isExpanded) { using (new EditorGUI.IndentLevelScope()) { Rect labelRect = EditorGUI.IndentedRect(headerRect); labelRect.y = labelY; Rect valueRect = labelRect; valueRect.xMin = labelRect.xMax; valueRect.xMax = position.xMax - copyIcon.width - 7; GUI.Label(labelRect, new GUIContent("GUID")); GUI.Label(valueRect, eventReference.Guid.ToString()); Rect copyRect = valueRect; copyRect.xMin = valueRect.xMax; copyRect.xMax = position.xMax; if (GUI.Button(copyRect, new GUIContent(copyIcon, L10n.Tr("Copy To Clipboard")))) { EditorGUIUtility.systemCopyBuffer = eventReference.Guid.ToString(); } valueRect.xMax = position.xMax; labelRect.y += baseHeight; valueRect.y += baseHeight; GUI.Label(labelRect, new GUIContent(L10n.Tr("Banks"))); GUI.Label(valueRect, string.Join(", ", editorEventRef.Banks.Select(x => x.Name).ToArray())); labelRect.y += baseHeight; valueRect.y += baseHeight; GUI.Label(labelRect, new GUIContent(L10n.Tr("Panning"))); GUI.Label(valueRect, editorEventRef.Is3D ? "3D" : "2D"); labelRect.y += baseHeight; valueRect.y += baseHeight; GUI.Label(labelRect, new GUIContent(L10n.Tr("Stream"))); GUI.Label(valueRect, editorEventRef.IsStream.ToString()); labelRect.y += baseHeight; valueRect.y += baseHeight; GUI.Label(labelRect, new GUIContent(L10n.Tr("Oneshot"))); GUI.Label(valueRect, editorEventRef.IsOneShot.ToString()); labelRect.y += baseHeight; valueRect.y += baseHeight; } } } else { EditorEventRef renamedEvent = GetRenamedEventRef(eventReference); if (renamedEvent != null) { MismatchInfo mismatch = new MismatchInfo() { Message = string.Format(L10n.Tr("Moved to {0}"), renamedEvent.Path), HelpText = string.Format( L10n.Tr("This event has been moved in FMOD Studio.\nYou can click the repair button to update the path to the new location, or run the {0} command to scan your project for similar issues and fix them all."), EventReferenceUpdater.MenuPath), RepairTooltip = string.Format(L10n.Tr("Repair: set path to {0}"), renamedEvent.Path), RepairAction = (p) => { p.FindPropertyRelative("Path").stringValue = renamedEvent.Path; }, }; using (new EditorGUI.IndentLevelScope()) { Rect mismatchRect = pathRect; mismatchRect.xMin = position.xMin; mismatchRect.xMax = position.xMax; mismatchRect.y += baseHeight; mismatchRect.height = baseHeight; mismatchRect = EditorGUI.IndentedRect(mismatchRect); DrawMismatchUI(mismatchRect, openRect.x, openRect.width, mismatch, property); } } else { Rect labelRect = pathRect; labelRect.xMax = position.xMax; labelRect.y += baseHeight; labelRect.height = WarningSize().y; GUI.Label(labelRect, NotFoundWarning); } } } } private static void HandleDragEvents(Rect position, SerializedProperty property) { Event e = Event.current; if (e.type == EventType.DragPerform && position.Contains(e.mousePosition)) { if (DragAndDrop.objectReferences.Length > 0 && DragAndDrop.objectReferences[0] != null && DragAndDrop.objectReferences[0].GetType() == typeof(EditorEventRef)) { EditorEventRef eventRef = DragAndDrop.objectReferences[0] as EditorEventRef; property.SetEventReference(eventRef.Guid, eventRef.Path); GUI.changed = true; e.Use(); } } if (e.type == EventType.DragUpdated && position.Contains(e.mousePosition)) { if (DragAndDrop.objectReferences.Length > 0 && DragAndDrop.objectReferences[0] != null && DragAndDrop.objectReferences[0].GetType() == typeof(EditorEventRef)) { DragAndDrop.visualMode = DragAndDropVisualMode.Move; DragAndDrop.AcceptDrag(); e.Use(); } } } private class MismatchInfo { public string Message; public string HelpText; public string RepairTooltip; public Action RepairAction; } private static void DrawMismatchUI(Rect rect, float repairButtonX, float repairButtonWidth, MismatchInfo mismatch, SerializedProperty property) { rect = EditorUtils.DrawHelpButton(rect, () => new SimpleHelp(mismatch.HelpText, 400)); Rect repairRect = new Rect(repairButtonX, rect.y, repairButtonWidth, GetBaseHeight()); if (GUI.Button(repairRect, new GUIContent(RepairIcon, mismatch.RepairTooltip), buttonStyle)) { mismatch.RepairAction(property); } Rect labelRect = rect; labelRect.xMax = repairRect.xMin; GUI.Label(labelRect, new GUIContent(mismatch.Message, WarningIcon)); } private static MismatchInfo GetMismatch(EventReference eventReference, EditorEventRef editorEventRef) { if (EventManager.GetEventLinkage(eventReference) == EventLinkage.Path) { if (eventReference.Guid != editorEventRef.Guid) { return new MismatchInfo() { Message = L10n.Tr("GUID doesn't match path"), HelpText = string.Format( L10n.Tr("The GUID on this EventReference doesn't match the path.\nYou can click the repair button to update the GUID to match the path, or run the {0} command to scan your project for similar issues and fix them all."), EventReferenceUpdater.MenuPath), RepairTooltip = string.Format(L10n.Tr("Repair: set GUID to {0}"), editorEventRef.Guid), RepairAction = (property) => { property.FindPropertyRelative("Guid").SetGuid(editorEventRef.Guid); }, }; } } else // EventLinkage.GUID { if (eventReference.Path != editorEventRef.Path) { return new MismatchInfo() { Message = L10n.Tr("Path doesn't match GUID"), HelpText = string.Format( L10n.Tr("The path on this EventReference doesn't match the GUID.\nYou can click the repair button to update the path to match the GUID, or run the {0} command to scan your project for similar issues and fix them all."), EventReferenceUpdater.MenuPath), RepairTooltip = string.Format(L10n.Tr("Repair: set path to '{0}'"), editorEventRef.Path), RepairAction = (property) => { property.FindPropertyRelative("Path").stringValue = editorEventRef.Path; }, }; } } return null; } private static void SetEvent(SerializedProperty property, string path) { EditorEventRef eventRef = EventManager.EventFromPath(path); if (eventRef != null) { property.SetEventReference(eventRef.Guid, eventRef.Path); } else { property.SetEventReference(new FMOD.GUID(), path); } } private static SerializedProperty GetGuidProperty(SerializedProperty property) { return property.FindPropertyRelative("Guid"); } private static SerializedProperty GetPathProperty(SerializedProperty property) { return property.FindPropertyRelative("Path"); } private static EditorEventRef GetEditorEventRef(EventReference eventReference) { if (EventManager.GetEventLinkage(eventReference) == EventLinkage.Path) { return EventManager.EventFromPath(eventReference.Path); } else // Assume EventLinkage.GUID { return EventManager.EventFromGUID(eventReference.Guid); } } private static EditorEventRef GetRenamedEventRef(EventReference eventReference) { if (Settings.Instance.EventLinkage == EventLinkage.Path && !eventReference.Guid.IsNull) { EditorEventRef editorEventRef = EventManager.EventFromGUID(eventReference.Guid); if (editorEventRef != null && editorEventRef.Path != eventReference.Path) { return editorEventRef; } } return null; } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { float baseHeight = GetBaseHeight(); EventReference eventReference = property.GetEventReference(); EditorEventRef editorEventRef = GetEditorEventRef(eventReference); if (editorEventRef == null) { return baseHeight + WarningSize().y; } else { float height; if (property.isExpanded) { height = baseHeight * 6; // 5 lines of info } else { height = baseHeight; } if (GetMismatch(eventReference, editorEventRef) != null) { height += WarningSize().y; } return height; } } } #pragma warning disable 0618 // Suppress the warning about using the obsolete EventRefAttribute class [CustomPropertyDrawer(typeof(EventRefAttribute))] #pragma warning restore 0618 public class LegacyEventRefDrawer : PropertyDrawer { private GUIStyle RichTextStyle; private static readonly string HelpText = string.Format(L10n.Tr("This field has the [EventRef] attribute, which is obsolete.\nTo resolve this issue:\n* Add a field of type EventReference to this class\nSet the MigrateTo property on the [EventRef] attribute: [EventRef(MigrateTo=\"\")]\n Run the {0} command to automatically migrate values from this field to the EventReference field"), EventReferenceUpdater.MenuPath); private static readonly Texture InfoIcon = EditorGUIUtility.IconContent("console.infoicon.sml").image; private static readonly Texture WarningIcon = EditorUtils.LoadImage("NotFound.png"); private void AffirmStyles() { if (RichTextStyle == null) { RichTextStyle = new GUIStyle(GUI.skin.label) { richText = true }; } } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { AffirmStyles(); label = EditorGUI.BeginProperty(position, label, property); Rect pathRect = position; pathRect.height = EditorGUIUtility.singleLineHeight; pathRect = EditorGUI.PrefixLabel(pathRect, label); EditorGUI.PropertyField(pathRect, property, GUIContent.none); using (new EditorGUI.IndentLevelScope()) { GUIContent content = StatusContent(property); Rect infoRect = EditorGUI.IndentedRect(position); infoRect.y = pathRect.yMax; infoRect.height = StatusSize(content).y; infoRect = EditorUtils.DrawHelpButton(infoRect, () => new SimpleHelp(HelpText, 400)); GUI.Label(infoRect, content, RichTextStyle); } EditorGUI.EndProperty(); } private GUIContent StatusContent(SerializedProperty property) { #pragma warning disable 0618 // Suppress the warning about using the obsolete EventRefAttribute class string migrationTarget = (attribute as EventRefAttribute).MigrateTo; #pragma warning restore 0618 if (string.IsNullOrEmpty(migrationTarget)) { return new GUIContent(L10n.Tr("[EventRef] is obsolete - use the EventReference type instead."), WarningIcon); } else { int parentPathLength = property.propertyPath.LastIndexOf('.'); if (parentPathLength >= 0) { migrationTarget = string.Format("{0}.{1}", property.propertyPath.Remove(parentPathLength), migrationTarget); } SerializedProperty targetProperty = property.serializedObject.FindProperty(migrationTarget); if (targetProperty != null) { return new GUIContent(string.Format(L10n.Tr("Will be migrated to {0}"), targetProperty.displayName), InfoIcon); } else { return new GUIContent(string.Format(L10n.Tr("Migration target {0} is missing"), migrationTarget), WarningIcon); } } } private Vector2 StatusSize(GUIContent content) { AffirmStyles(); return Vector2.Max(RichTextStyle.CalcSize(content), EditorUtils.GetHelpButtonSize()); } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { return EditorGUIUtility.singleLineHeight + StatusSize(StatusContent(property)).y; } } }