diff --git a/Assets/NaughtyAttributes/Documentation~/AdvancedDropdown_Inspector.png b/Assets/NaughtyAttributes/Documentation~/AdvancedDropdown_Inspector.png new file mode 100644 index 00000000..828b30b1 Binary files /dev/null and b/Assets/NaughtyAttributes/Documentation~/AdvancedDropdown_Inspector.png differ diff --git a/Assets/NaughtyAttributes/Samples/DemoScene/DemoScene.unity b/Assets/NaughtyAttributes/Samples/DemoScene/DemoScene.unity index 7b9945cb..c7d4d991 100644 --- a/Assets/NaughtyAttributes/Samples/DemoScene/DemoScene.unity +++ b/Assets/NaughtyAttributes/Samples/DemoScene/DemoScene.unity @@ -43,7 +43,7 @@ RenderSettings: --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 - serializedVersion: 12 + serializedVersion: 11 m_GIWorkflowMode: 0 m_GISettings: serializedVersion: 2 @@ -98,8 +98,7 @@ LightmapSettings: m_TrainingDataDestination: TrainingData m_LightProbeSampleCountMultiplier: 4 m_LightingDataAsset: {fileID: 0} - m_LightingSettings: {fileID: 4890085278179872738, guid: 98ee975b74776234986f4d35f14c4ccc, - type: 2} + m_UseShadowmask: 1 --- !u!196 &4 NavMeshSettings: serializedVersion: 2 @@ -119,8 +118,6 @@ NavMeshSettings: manualTileSize: 0 tileSize: 256 accuratePlacement: 0 - maxJobWorkers: 0 - preserveTilesOutsideBounds: 0 debug: m_Flags: 0 m_NavMeshData: {fileID: 0} @@ -184,7 +181,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 23 + m_RootOrder: 24 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &114650325 MonoBehaviour: @@ -267,7 +264,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 9 + m_RootOrder: 10 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &155697337 MonoBehaviour: @@ -315,7 +312,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 12 + m_RootOrder: 13 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &237121642 MonoBehaviour: @@ -363,7 +360,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 29 + m_RootOrder: 30 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &369789278 MonoBehaviour: @@ -411,7 +408,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 22 + m_RootOrder: 23 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &572382751 MonoBehaviour: @@ -464,7 +461,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 10 + m_RootOrder: 11 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &607441875 MonoBehaviour: @@ -554,7 +551,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 20 + m_RootOrder: 21 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &779670086 GameObject: @@ -585,7 +582,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 4 + m_RootOrder: 5 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &779670088 MonoBehaviour: @@ -633,7 +630,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 15 + m_RootOrder: 16 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &892661556 MonoBehaviour: @@ -681,7 +678,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 16 + m_RootOrder: 17 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &933563386 MonoBehaviour: @@ -733,7 +730,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 11 + m_RootOrder: 12 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &948946035 MonoBehaviour: @@ -783,7 +780,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 7 + m_RootOrder: 8 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1139446981 MonoBehaviour: @@ -830,6 +827,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: - {fileID: 106181863} + - {fileID: 1518588622} - {fileID: 1178133860} - {fileID: 2043059161} - {fileID: 2082563173} @@ -893,7 +891,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 1 + m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1178133861 MonoBehaviour: @@ -966,7 +964,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 18 + m_RootOrder: 19 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1194502639 MonoBehaviour: @@ -1009,7 +1007,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 14 + m_RootOrder: 15 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1202167596 MonoBehaviour: @@ -1081,7 +1079,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 25 + m_RootOrder: 26 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1293624597 MonoBehaviour: @@ -1129,7 +1127,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 19 + m_RootOrder: 20 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1380469386 MonoBehaviour: @@ -1269,7 +1267,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 30 + m_RootOrder: 31 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1463483879 MonoBehaviour: @@ -1288,6 +1286,56 @@ MonoBehaviour: int1: 0 nest2: int2: 0 +--- !u!1 &1518588621 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1518588622} + - component: {fileID: 1518588623} + m_Layer: 0 + m_Name: AdvancedDropdown + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1518588622 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1518588621} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1148579784} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1518588623 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1518588621} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 13a8968aeeea0f14aa145ca2d6e3468c, type: 3} + m_Name: + m_EditorClassIdentifier: + intValue: 1 + intValue2: 1 + nest1: + stringValue: B + nest2: + vectorValue: {x: -1, y: 0, z: 0} + vectorValue2: {x: -1, y: 0, z: 0} --- !u!1 &1524906390 GameObject: m_ObjectHideFlags: 0 @@ -1318,7 +1366,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 24 + m_RootOrder: 25 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1524906392 MonoBehaviour: @@ -1401,7 +1449,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 8 + m_RootOrder: 9 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1552114401 MonoBehaviour: @@ -1541,7 +1589,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 6 + m_RootOrder: 7 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1622801697 MonoBehaviour: @@ -1588,7 +1636,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 27 + m_RootOrder: 28 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1706612701 GameObject: @@ -1619,7 +1667,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 28 + m_RootOrder: 29 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1706612703 MonoBehaviour: @@ -1673,7 +1721,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 13 + m_RootOrder: 14 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1747151589 MonoBehaviour: @@ -1734,7 +1782,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 21 + m_RootOrder: 22 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1784643786 MonoBehaviour: @@ -1787,7 +1835,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 26 + m_RootOrder: 27 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1871127831 MonoBehaviour: @@ -1835,7 +1883,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 17 + m_RootOrder: 18 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1989156460 MonoBehaviour: @@ -1878,7 +1926,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 5 + m_RootOrder: 6 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &2015414599 MonoBehaviour: @@ -1926,7 +1974,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 2 + m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &2043059162 MonoBehaviour: @@ -1970,7 +2018,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 1148579784} - m_RootOrder: 3 + m_RootOrder: 4 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &2082563174 MonoBehaviour: diff --git a/Assets/NaughtyAttributes/Scripts/Core/DrawerAttributes/AdvancedDropdownAttribute.cs b/Assets/NaughtyAttributes/Scripts/Core/DrawerAttributes/AdvancedDropdownAttribute.cs new file mode 100644 index 00000000..e264c50b --- /dev/null +++ b/Assets/NaughtyAttributes/Scripts/Core/DrawerAttributes/AdvancedDropdownAttribute.cs @@ -0,0 +1,30 @@ +using System.Collections; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace NaughtyAttributes +{ + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public class AdvancedDropdownAttribute : DrawerAttribute + { + public string ValuesName { get; private set; } + public bool KeepStateWhenReopened { get; set; } + public bool MaximizeWidth { get; set; } + public int MinHeight { get; set; } = -1; + public int MaxHeight { get; set; } = -1; + public bool FlattenTree { get; set; } + public bool DrawOnSameLine { get; set; } + public string ListTitle { get; set; } = "Options"; + + //public SortByMode SortBy { get; set; } = SortByMode.Name | SortByMode.BranchesFirst; + + //[Flags] + //public enum SortByMode { None, Name = 1 , BranchesFirst = 2, LeafsFirst = 4}; + + public AdvancedDropdownAttribute(string valuesName) + { + ValuesName = valuesName; + } + } +} diff --git a/Assets/NaughtyAttributes/Scripts/Core/DrawerAttributes/AdvancedDropdownAttribute.cs.meta b/Assets/NaughtyAttributes/Scripts/Core/DrawerAttributes/AdvancedDropdownAttribute.cs.meta new file mode 100644 index 00000000..430879c8 --- /dev/null +++ b/Assets/NaughtyAttributes/Scripts/Core/DrawerAttributes/AdvancedDropdownAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a0458856d3d65b94e98a6811e957eb7e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NaughtyAttributes/Scripts/Editor/PropertyDrawers/AdvancedDropdownPropertyDrawer.cs b/Assets/NaughtyAttributes/Scripts/Editor/PropertyDrawers/AdvancedDropdownPropertyDrawer.cs new file mode 100644 index 00000000..000eee55 --- /dev/null +++ b/Assets/NaughtyAttributes/Scripts/Editor/PropertyDrawers/AdvancedDropdownPropertyDrawer.cs @@ -0,0 +1,386 @@ +using UnityEngine; +using UnityEditor; +using System.Collections; +using System.Reflection; +using System; +using System.Collections.Generic; +using UnityEditor.IMGUI.Controls; +using System.Linq; +using UnityEngine.UI; +using UnityEngine.UIElements; + +namespace NaughtyAttributes.Editor +{ + [CustomPropertyDrawer(typeof(AdvancedDropdownAttribute))] + public class AdvancedDropdownPropertyDrawer : PropertyDrawerBase + { + private AdvancedDropdownState _dropdownState; + + public class AdvancedDropdownImpl : AdvancedDropdown + { + public struct Options + { + public IList Values { get; internal set; } + public string SelectedValue { get; internal set; } + public Action ItemSelected { get; internal set; } + public bool FlattenTree { get; internal set; } + public int MinHeight { get; internal set; } + public int MaxHeight { get; internal set; } + public bool MaximizeWidth { get; internal set; } + public bool KeepStateWhenReopened { get; internal set; } + public bool DrawOnSameLine { get; internal set; } + public string Title { get; internal set; } + } + + private Options _options; + + public AdvancedDropdownImpl(AdvancedDropdownState state, Options options) : base(state) + { + _options = options; + } + + protected override AdvancedDropdownItem BuildRoot() + { + var itemNames = _options.Values.Select(o => o.ToString()); + + var root = MakeTreeFromPaths(itemNames, _options.Title ?? "Options", _options.FlattenTree ? '\0' : '/'); + + return root; + } + + public AdvancedDropdownItem MakeTreeFromPaths(IEnumerable paths, string rootNodeName = "", char separator = '/') + { + int id = 0; + var rootNode = new AdvancedDropdownItem(rootNodeName); + foreach (var path in paths.Where(x => !string.IsNullOrEmpty(x.Trim()))) + { + var currentNode = rootNode; + var pathItems = path.Split(separator); + for (int i = 0; i < pathItems.Length; i++) + { + var pathItem = pathItems[i]; + var tmp = currentNode.children.FirstOrDefault(x => x.name.Equals(pathItem)); + + var currentId = -1; + Texture2D icon = null; + if (i == pathItems.Length - 1) + { + currentId = id++; + if (path == _options.SelectedValue.ToString()) + icon = EditorGUIUtility.IconContent("FilterSelectedOnly").image as Texture2D; + } + + //var type = Type.GetType(pathItem, false, true); + //if (type != null) + //{ + // icon = EditorGUIUtility.ObjectContent(null, type).image as Texture2D; + //} + + currentNode = tmp ?? AppendItem(currentNode, pathItem, currentId, icon); + } + } + return rootNode; + } + + private static AdvancedDropdownItem AppendItem(AdvancedDropdownItem currentNode, string item, int id, Texture2D icon = null) + { + var ditem = new AdvancedDropdownItem(item) + { + id = id, + icon = icon + }; + + currentNode.AddChild(ditem); + return ditem; + } + + protected override void ItemSelected(AdvancedDropdownItem item) + { + base.ItemSelected(item); + _options.ItemSelected(item.id); + } + + public new void Show(Rect rect) + { + base.Show(rect); + SetHeightForPopup(rect, _options.MinHeight, _options.MaxHeight); + } + + // based upon https://forum.unity.com/threads/add-maximum-window-size-to-advanceddropdown-control.724229/ + private static void SetHeightForPopup(Rect rect, float minHeight, float maxHeight) + { + var window = EditorWindow.focusedWindow; + + if (window == null) + { + Debug.LogWarning("EditorWindow.focusedWindow was null."); + return; + } + + if (!string.Equals(window.GetType().Namespace, typeof(AdvancedDropdown).Namespace)) + { + Debug.LogWarning("EditorWindow.focusedWindow " + EditorWindow.focusedWindow.GetType().FullName + " was not in expected namespace."); + return; + } + + var position = window.position; + var hasChanges = false; + + if (minHeight > -1 && position.height < minHeight) + { + position.height = minHeight; + hasChanges = true; + } + + if (maxHeight > -1 && position.height > maxHeight) + { + position.height = maxHeight; + hasChanges = true; + } + + if (hasChanges == false) + return; + + window.minSize = position.size; + window.maxSize = position.size; + window.position = position; + window.ShowAsDropDown(GUIUtility.GUIToScreenRect(rect), position.size); + } + } + + protected override float GetPropertyHeight_Internal(SerializedProperty property, GUIContent label) + { + AdvancedDropdownAttribute dropdownAttribute = (AdvancedDropdownAttribute)attribute; + object valuesObject = GetValuesObject(property, dropdownAttribute.ValuesName); + FieldInfo fieldInfo = ReflectionUtility.GetField(PropertyUtility.GetTargetObjectWithProperty(property), property.name); + + float propertyHeight = IsValuesObjectValid(valuesObject, fieldInfo) + ? GetPropertyHeight(property) + : GetPropertyHeight(property) + GetHelpBoxHeight(); + + return propertyHeight; + } + + protected override void OnGUI_Internal(Rect rect, SerializedProperty property, GUIContent label) + { + EditorGUI.BeginProperty(rect, label, property); + + AdvancedDropdownAttribute dropdownAttribute = (AdvancedDropdownAttribute)attribute; + object target = PropertyUtility.GetTargetObjectWithProperty(property); + + object valuesObject = GetValuesObject(property, dropdownAttribute.ValuesName); + FieldInfo dropdownField = ReflectionUtility.GetField(target, property.name); + + if (TryExtractValues(target, valuesObject, dropdownField, out IList values, out IList displayOptions, out int selectedValueIndex, out string selectedValueString)) + { + ShowAdvancedDropdown(rect, label, new AdvancedDropdownImpl.Options + { + Values = displayOptions, + SelectedValue = selectedValueString, + FlattenTree = dropdownAttribute.FlattenTree, + DrawOnSameLine = dropdownAttribute.DrawOnSameLine, + MinHeight = dropdownAttribute.MinHeight, + MaxHeight = dropdownAttribute.MaxHeight, + MaximizeWidth = dropdownAttribute.MaximizeWidth, + KeepStateWhenReopened = dropdownAttribute.KeepStateWhenReopened, + Title = dropdownAttribute.ListTitle, + ItemSelected = selectedIndex => + { + if (selectedIndex == selectedValueIndex) + return; + + Undo.RecordObject(property.serializedObject.targetObject, "Advanced dropdown"); + + dropdownField.SetValue(target, values[selectedIndex]); + property.serializedObject.ApplyModifiedProperties(); + + PropertyUtility.CallOnValueChangedCallbacks(property); + } + }); + + } + else + { + string message = string.Format("Invalid values with name '{0}' provided to '{1}'. Either the values name is incorrect or the types of the target field and the values field/property/method don't match", + dropdownAttribute.ValuesName, dropdownAttribute.GetType().Name); + + DrawDefaultPropertyAndHelpBox(rect, property, message, MessageType.Warning); + } + + EditorGUI.EndProperty(); + } + + private static bool TryExtractValues(object target, object valuesObject, FieldInfo dropdownField, out IList values, out IList displayOptions, out int selectedValueIndex, out string selectedValueString) + { + values = null; + displayOptions = null; + selectedValueIndex = -1; + selectedValueString = ""; + + if (IsValuesObjectValid(valuesObject, dropdownField) == false) + return false; + + if (valuesObject is IList) + { + ExtractValuesFromList(target, valuesObject, dropdownField, out values, out displayOptions, out selectedValueIndex, out selectedValueString); + return true; + } + + if(valuesObject is IDropdownList) + { + ExtractValuesFromDropdownList(target, valuesObject, dropdownField, out values, out displayOptions, out selectedValueIndex, out selectedValueString); + return true; + } + + return false; + } + + + private static void ExtractValuesFromList(object target, object valuesObject, FieldInfo dropdownField, out IList values, out IList displayOptions, out int selectedValueIndex, out string selectedValueString) + { + // Selected value + object selectedValue = dropdownField.GetValue(target); + + // Values and display options + IList valuesList = (IList)valuesObject; + values = new object[valuesList.Count]; + displayOptions = new string[valuesList.Count]; + for (int i = 0; i < valuesList.Count; i++) + { + object value = valuesList[i]; + values[i] = value; + displayOptions[i] = value == null ? "" : value.ToString(); + } + + // Selected value index + selectedValueIndex = values.IndexOf(selectedValue); + + selectedValueString = selectedValue?.ToString() ?? ""; + } + + + private static void ExtractValuesFromDropdownList(object target, object valuesObject, FieldInfo dropdownField, out IList values, out IList displayOptions, out int selectedValueIndex, out string selectedValueString) + { + // Current value + object selectedValue = dropdownField.GetValue(target); + + // Current value index, values and display options + int index = -1; + selectedValueIndex = -1; + selectedValueString = null; + + values = new List(); + displayOptions = new List(); + IDropdownList dropdown = (IDropdownList)valuesObject; + + using (IEnumerator> dropdownEnumerator = dropdown.GetEnumerator()) + { + while (dropdownEnumerator.MoveNext()) + { + index++; + + KeyValuePair current = dropdownEnumerator.Current; + if (current.Value?.Equals(selectedValue) == true) + { + selectedValueIndex = index; + selectedValueString = current.Key; + } + + values.Add(current.Value); + + if (current.Key == null) + { + displayOptions.Add(""); + } + else if (string.IsNullOrWhiteSpace(current.Key)) + { + displayOptions.Add(""); + } + else + { + displayOptions.Add(current.Key); + } + } + } + + if (selectedValueIndex == -1) + selectedValueString = selectedValue?.ToString() ?? ""; + } + + private void ShowAdvancedDropdown(Rect rect, GUIContent label, AdvancedDropdownImpl.Options options) + { + // inspired by https://forum.unity.com/threads/freebie-filedropdown-implementation-using-unityeditor-imgui-controls-advanceddropdown.792477/ + + var propertyRect = rect; + var labelRect = rect; + labelRect.width -= EditorGUIUtility.labelWidth; + propertyRect.width -= EditorGUIUtility.labelWidth + 5; + propertyRect.x = EditorGUIUtility.labelWidth + 20; + + EditorGUI.LabelField(labelRect, label); + if (EditorGUI.DropdownButton(propertyRect, new GUIContent(options.SelectedValue), FocusType.Keyboard)) + { + if (options.KeepStateWhenReopened == false || _dropdownState == null) + _dropdownState = new AdvancedDropdownState(); + + var dropdownRect = options.MaximizeWidth ? rect : propertyRect; + if (options.DrawOnSameLine == true) + dropdownRect.height = 0; + + var dropdown = new AdvancedDropdownImpl(_dropdownState, options); + dropdown.Show(dropdownRect); + } + } + + private object GetValuesObject(SerializedProperty property, string valuesName) + { + object target = PropertyUtility.GetTargetObjectWithProperty(property); + + FieldInfo valuesFieldInfo = ReflectionUtility.GetField(target, valuesName); + if (valuesFieldInfo != null) + { + return valuesFieldInfo.GetValue(target); + } + + PropertyInfo valuesPropertyInfo = ReflectionUtility.GetProperty(target, valuesName); + if (valuesPropertyInfo != null) + { + return valuesPropertyInfo.GetValue(target); + } + + MethodInfo methodValuesInfo = ReflectionUtility.GetMethod(target, valuesName); + if (methodValuesInfo != null && + methodValuesInfo.ReturnType != typeof(void) && + methodValuesInfo.GetParameters().Length == 0) + { + return methodValuesInfo.Invoke(target, null); + } + + return null; + } + + private static bool IsValuesObjectValid(object values, FieldInfo dropdownField) + { + if (values == null || dropdownField == null) + { + return false; + } + + if ((values is IList && dropdownField.FieldType == GetElementType(values)) || + (values is IDropdownList)) + { + return true; + } + + return false; + } + + private static Type GetElementType(object values) + { + Type valuesType = values.GetType(); + Type elementType = ReflectionUtility.GetListElementType(valuesType); + + return elementType; + } + } +} \ No newline at end of file diff --git a/Assets/NaughtyAttributes/Scripts/Editor/PropertyDrawers/AdvancedDropdownPropertyDrawer.cs.meta b/Assets/NaughtyAttributes/Scripts/Editor/PropertyDrawers/AdvancedDropdownPropertyDrawer.cs.meta new file mode 100644 index 00000000..e29c61c9 --- /dev/null +++ b/Assets/NaughtyAttributes/Scripts/Editor/PropertyDrawers/AdvancedDropdownPropertyDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 32c02467dd0bd2a4993ebe5e2ed329da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NaughtyAttributes/Scripts/Test/AdvancedDropdownTest.cs b/Assets/NaughtyAttributes/Scripts/Test/AdvancedDropdownTest.cs new file mode 100644 index 00000000..e0cb422b --- /dev/null +++ b/Assets/NaughtyAttributes/Scripts/Test/AdvancedDropdownTest.cs @@ -0,0 +1,69 @@ +using UnityEngine; +using System.Collections.Generic; +using System; + +namespace NaughtyAttributes.Test +{ + public class AdvancedDropdownTest : MonoBehaviour + { + [AdvancedDropdown("intValues", MinHeight = 150, DrawOnSameLine = true)] + [OnValueChanged(nameof(OnValueChanged))] + public int intValue; + + private void OnValueChanged() + { + intValue2 = intValue; + } + + public int intValue2; + +#pragma warning disable 414 + private int[] intValues = new int[] { 1, 2, 3, 4 }; +#pragma warning restore 414 + + public AdvancedDropdownNest1 nest1; + } + + [System.Serializable] + public class AdvancedDropdownNest1 + { + [AdvancedDropdown("StringValues")] + public string stringValue; + + private List StringValues { get { return new List() { "A", "B", "C" }; } } + + public AdvancedDropdownNest2 nest2; + } + + [System.Serializable] + public class AdvancedDropdownNest2 + { + [AdvancedDropdown("GetVectorValues", ListTitle = "the best vectors")] + [OnValueChanged(nameof(OnValueChanged))] + public Vector3 vectorValue; + + [AdvancedDropdown("GetVectorValues", FlattenTree = true)] + [OnValueChanged(nameof(OnValueChanged))] + public Vector3 vectorValue3; + + private void OnValueChanged() + { + vectorValue2 = vectorValue; + } + + private DropdownList GetVectorValues() + { + return new DropdownList() + { + { "Right", Vector3.right }, + { "Up", Vector3.up }, + { "Forward", Vector3.forward }, + { "Others/Left", Vector3.left }, + { "Others/Down", Vector3.down }, + { "Others/Back", Vector3.back }, + }; + } + + public Vector3 vectorValue2; + } +} diff --git a/Assets/NaughtyAttributes/Scripts/Test/AdvancedDropdownTest.cs.meta b/Assets/NaughtyAttributes/Scripts/Test/AdvancedDropdownTest.cs.meta new file mode 100644 index 00000000..f69907b1 --- /dev/null +++ b/Assets/NaughtyAttributes/Scripts/Test/AdvancedDropdownTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 13a8968aeeea0f14aa145ca2d6e3468c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md index 4def42e1..4ecb4d78 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,58 @@ NaughtyAttributes is an open-source project that I am developing in my free time Provide special draw options to serialized fields. A field can have only one DrawerAttribute. If a field has more than one, only the bottom one will be used. +### AdvancedDropdown +Provides an advanced interface for dropdown value selection, search and tree view navigation. +Useful for long lists or trees. Tree nodes have to be separated by `/`. + +Makes use of Unity's AdvancedDropdown class used e.g. for the "Add Component" menu in the inspector. + +```csharp +public class _NaughtyComponent : MonoBehaviour +{ + [AdvancedDropdown("intValues", MinHeight = 150)] + public int intValue; + + [AdvancedDropdown("StringValues", MinHeight = 150)] + public string stringValue; + + [AdvancedDropdown("GetVectorValues", MinHeight = 150)] + public Vector3 vectorValue; + + private int[] intValues = new int[] { 1, 2, 3, 4, 5 }; + + private List StringValues + { + get + { + return new List() + { + "A/A1", + "A/A2", + "B/B1", + "B/B2", + "B/B3" + }; + } + } + + private DropdownList GetVectorValues() + { + return new DropdownList() + { + { "Right", Vector3.right }, + { "Left", Vector3.left }, + { "Up", Vector3.up }, + { "Others/Down", Vector3.down }, + { "Others/Forward", Vector3.forward }, + { "Others/Back", Vector3.back } + }; + } +} +``` +![inspector](https://github.com/dbrizov/NaughtyAttributes/blob/master/Assets/NaughtyAttributes/Documentation~/AdvancedDropdown_Inspector.png) +![inspector](https://github.com/krisrok/NaughtyAttributes/blob/advanceddropdown/Assets/NaughtyAttributes/Documentation~/AdvancedDropdown_Inspector.png) + ### AnimatorParam Select an Animator paramater via dropdown interface.