using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEditor; using UnityEngine; namespace UnityTest { public class PropertyPathSelector { private readonly DropDownControl m_ThisDropDown = new DropDownControl(); private readonly Func m_ReplaceDotWithSlashAndAddGoGroup = s => s.Replace('.', '/'); private readonly string m_Name; private bool m_FocusBackToEdit; private SelectedPathError m_Error; public PropertyPathSelector(string name) { m_Name = name; m_ThisDropDown.convertForGUIContent = m_ReplaceDotWithSlashAndAddGoGroup; m_ThisDropDown.tooltip = "Select the path to the value you want to use for comparison."; } public void Draw(GameObject go, ActionBase comparer, string propertPath, Type[] accepatbleTypes, Action onSelectedGo, Action onSelectedPath) { var newGo = (GameObject)EditorGUILayout.ObjectField(m_Name, go, typeof(GameObject), true); if (newGo != go) onSelectedGo(newGo); if (go != null) { var newPath = DrawListOfMethods(go, comparer, propertPath, accepatbleTypes, m_ThisDropDown); if (newPath != propertPath) onSelectedPath(newPath); } } private string DrawListOfMethods(GameObject go, ActionBase comparer, string propertPath, Type[] accepatbleTypes, DropDownControl dropDown) { string result = propertPath; if (accepatbleTypes == null) { result = DrawManualPropertyEditField(go, propertPath, accepatbleTypes, dropDown); } else { bool isPropertyOrFieldFound = true; if (string.IsNullOrEmpty(result)) { var options = GetFieldsAndProperties(go, comparer, result, accepatbleTypes); isPropertyOrFieldFound = options.Any(); if (isPropertyOrFieldFound) { result = options.First(); } } if (isPropertyOrFieldFound) { dropDown.Draw(go.name + '.', result, () => { try { var options = GetFieldsAndProperties(go, comparer, result, accepatbleTypes); return options.ToArray(); } catch (Exception) { Debug.LogWarning("An exception was thrown while resolving a property list. Resetting property path."); result = ""; return new string[0]; } }, s => result = s); } else { result = DrawManualPropertyEditField(go, propertPath, accepatbleTypes, dropDown); } } return result; } private static List GetFieldsAndProperties(GameObject go, ActionBase comparer, string extendPath, Type[] accepatbleTypes) { var propertyResolver = new PropertyResolver {AllowedTypes = accepatbleTypes, ExcludedFieldNames = comparer.GetExcludedFieldNames()}; var options = propertyResolver.GetFieldsAndPropertiesFromGameObject(go, comparer.GetDepthOfSearch(), extendPath).ToList(); options.Sort((x, y) => { if (char.IsLower(x[0])) return -1; if (char.IsLower(y[0])) return 1; return x.CompareTo(y); }); return options; } private string DrawManualPropertyEditField(GameObject go, string propertPath, Type[] acceptableTypes, DropDownControl dropDown) { var propertyResolver = new PropertyResolver { AllowedTypes = acceptableTypes }; IList list; var loadProps = new Func(() => { try { list = propertyResolver.GetFieldsAndPropertiesUnderPath(go, propertPath); } catch (ArgumentException) { list = propertyResolver.GetFieldsAndPropertiesUnderPath(go, ""); } return list.ToArray(); }); EditorGUILayout.BeginHorizontal(); var labelSize = EditorStyles.label.CalcSize(new GUIContent(go.name + '.')); GUILayout.Label(go.name + (propertPath.Length > 0 ? "." : ""), EditorStyles.label, GUILayout.Width(labelSize.x)); string btnName = "hintBtn"; if (GUI.GetNameOfFocusedControl() == btnName && Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.DownArrow) { Event.current.Use(); dropDown.PrintMenu(loadProps()); GUI.FocusControl(""); m_FocusBackToEdit = true; } EditorGUI.BeginChangeCheck(); GUI.SetNextControlName(btnName); var result = GUILayout.TextField(propertPath, EditorStyles.textField); if (EditorGUI.EndChangeCheck()) { m_Error = DoesPropertyExist(go, result); } if (m_FocusBackToEdit) { m_FocusBackToEdit = false; GUI.FocusControl(btnName); } if (GUILayout.Button("Clear", EditorStyles.miniButton, GUILayout.Width(38))) { result = ""; GUI.FocusControl(null); m_FocusBackToEdit = true; m_Error = DoesPropertyExist(go, result); } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); GUILayout.Label("", GUILayout.Width(labelSize.x)); dropDown.Draw("", result ?? "", loadProps, s => { result = s; GUI.FocusControl(null); m_FocusBackToEdit = true; m_Error = DoesPropertyExist(go, result); }); EditorGUILayout.EndHorizontal(); switch (m_Error) { case SelectedPathError.InvalidPath: EditorGUILayout.HelpBox("This property does not exist", MessageType.Error); break; case SelectedPathError.MissingComponent: EditorGUILayout.HelpBox("This property or field is not attached or set. It will fail unless it will be attached before the check is perfomed.", MessageType.Warning); break; } return result; } private SelectedPathError DoesPropertyExist(GameObject go, string propertPath) { try { object obj; if (MemberResolver.TryGetValue(go, propertPath, out obj)) return SelectedPathError.None; return SelectedPathError.InvalidPath; } catch (TargetInvocationException e) { if (e.InnerException is MissingComponentException) return SelectedPathError.MissingComponent; throw; } } private enum SelectedPathError { None, MissingComponent, InvalidPath } } }