AssetReferenceDrawer.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Reflection;
  8. using System.Threading.Tasks;
  9. using UnityEditor.AddressableAssets.Settings;
  10. using UnityEditor.IMGUI.Controls;
  11. using UnityEditor.SceneManagement;
  12. using UnityEngine;
  13. using UnityEngine.AddressableAssets;
  14. using UnityEngine.Assertions;
  15. using UnityEngine.U2D;
  16. using Debug = UnityEngine.Debug;
  17. namespace UnityEditor.AddressableAssets.GUI
  18. {
  19. using Object = UnityEngine.Object;
  20. [CustomPropertyDrawer(typeof(AssetReference), true)]
  21. class AssetReferenceDrawer : PropertyDrawer
  22. {
  23. public string newGuid;
  24. public string newGuidPropertyPath;
  25. internal string m_AssetName;
  26. internal Rect assetDropDownRect;
  27. internal const string noAssetString = "None (AddressableAsset)";
  28. internal const string forceAddressableString = "Make Addressable - ";
  29. internal AssetReference m_AssetRefObject;
  30. internal GUIContent m_label;
  31. internal bool m_ReferencesSame = true;
  32. internal List<AssetReferenceUIRestrictionSurrogate> m_Restrictions = null;
  33. SubassetPopup m_SubassetPopup;
  34. private Texture2D m_CaretTexture = null;
  35. internal const string k_FieldControlName = "AssetReferenceField";
  36. internal List<AssetReferenceUIRestrictionSurrogate> Restrictions => m_Restrictions;
  37. /// <summary>
  38. /// Validates that the referenced asset allowable for this asset reference.
  39. /// </summary>
  40. /// <param name="path">The path to the asset in question.</param>
  41. /// <returns>Whether the referenced asset is valid.</returns>
  42. public bool ValidateAsset(string path)
  43. {
  44. return AssetReferenceDrawerUtilities.ValidateAsset(m_AssetRefObject, Restrictions, path);
  45. }
  46. internal bool ValidateAsset(IReferenceEntryData entryData)
  47. {
  48. return AssetReferenceDrawerUtilities.ValidateAsset(m_AssetRefObject, Restrictions, entryData);
  49. }
  50. /*
  51. * The AssetReference class is not a Unity.Object or a base type so a lot of SerializedProperty's
  52. * functionalities doesn't work, because type-checking is done in the API to check whether an operation
  53. * can be executed or not. In the engine, one of the way changes are detected is when a new value is set
  54. * through the class' value setters (see MarkPropertyModified() in SerializedProperty.cpp). So in order to
  55. * trigger a change, we modify a sub-property instead.
  56. */
  57. void TriggerOnValidate(SerializedProperty property)
  58. {
  59. if (property != null)
  60. {
  61. property.serializedObject.ApplyModifiedProperties();
  62. property.serializedObject.Update();
  63. // This is actually what triggers the OnValidate() method.
  64. // Since 'm_EditorAssetChanged' is of a recognized type and is a sub-property of AssetReference, both
  65. // are flagged as changed and OnValidate() is called.
  66. property.FindPropertyRelative("m_EditorAssetChanged").boolValue = false;
  67. }
  68. }
  69. public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
  70. {
  71. if (property == null || label == null)
  72. {
  73. Debug.LogError("Error rendering drawer for AssetReference property.");
  74. return;
  75. }
  76. string labelText = label.text;
  77. m_ReferencesSame = true;
  78. m_AssetRefObject = property.GetActualObjectForSerializedProperty<AssetReference>(fieldInfo, ref labelText);
  79. labelText = ObjectNames.NicifyVariableName(labelText);
  80. if (labelText != label.text || string.IsNullOrEmpty(label.text))
  81. {
  82. label = new GUIContent(labelText, label.tooltip);
  83. }
  84. m_label = label;
  85. if (m_AssetRefObject == null)
  86. {
  87. return;
  88. }
  89. EditorGUI.BeginProperty(position, label, property);
  90. if (m_Restrictions == null)
  91. m_Restrictions = AssetReferenceDrawerUtilities.GatherFilters(property);
  92. string guid = m_AssetRefObject.AssetGUID;
  93. var aaSettings = AddressableAssetSettingsDefaultObject.Settings;
  94. var isNotAddressable = ApplySelectionChanges(property, aaSettings, ref guid);
  95. assetDropDownRect = EditorGUI.PrefixLabel(position, label);
  96. var nameToUse = AssetReferenceDrawerUtilities.GetNameForAsset(ref m_ReferencesSame, property, isNotAddressable, fieldInfo, m_label.text);
  97. DrawSubassets(property);
  98. bool isDragging = Event.current.type == EventType.DragUpdated && position.Contains(Event.current.mousePosition);
  99. bool isDropping = Event.current.type == EventType.DragPerform && position.Contains(Event.current.mousePosition);
  100. DrawControl(property, nameToUse, isNotAddressable, guid);
  101. HandleDragAndDrop(property, isDragging, isDropping, guid);
  102. EditorGUI.EndProperty();
  103. }
  104. private void DrawSubassets(SerializedProperty property)
  105. {
  106. if (m_AssetRefObject.editorAsset != null && m_ReferencesSame)
  107. {
  108. List<Object> subAssets = null;
  109. bool hasSubAssets = !string.IsNullOrEmpty(m_AssetRefObject.SubObjectName);
  110. if (!hasSubAssets)
  111. {
  112. subAssets = AssetReferenceDrawerUtilities.GetSubAssetsList(m_AssetRefObject);
  113. hasSubAssets = subAssets.Count > 1;
  114. }
  115. if (hasSubAssets)
  116. {
  117. assetDropDownRect = DrawSubAssetsControl(property, subAssets);
  118. }
  119. }
  120. }
  121. bool ApplySelectionChanges(SerializedProperty property, AddressableAssetSettings aaSettings, ref string guid)
  122. {
  123. var checkToForceAddressable = string.Empty;
  124. if (!string.IsNullOrEmpty(newGuid) && newGuidPropertyPath == property.propertyPath)
  125. {
  126. if (newGuid == noAssetString)
  127. {
  128. if (AssetReferenceDrawerUtilities.SetObject(ref m_AssetRefObject, ref m_ReferencesSame, property, null, fieldInfo, m_label.text, out guid))
  129. TriggerOnValidate(property);
  130. newGuid = string.Empty;
  131. }
  132. else if (newGuid == forceAddressableString)
  133. {
  134. checkToForceAddressable = guid;
  135. newGuid = string.Empty;
  136. }
  137. else if (guid != newGuid)
  138. {
  139. if (AssetReferenceDrawerUtilities.SetObject(ref m_AssetRefObject, ref m_ReferencesSame, property, AssetDatabase.LoadAssetAtPath<Object>(AssetDatabase.GUIDToAssetPath(newGuid)), fieldInfo, m_label.text, out guid))
  140. {
  141. checkToForceAddressable = newGuid;
  142. TriggerOnValidate(property);
  143. }
  144. newGuid = string.Empty;
  145. }
  146. }
  147. bool isNotAddressable = false;
  148. m_AssetName = noAssetString;
  149. if (aaSettings != null && !string.IsNullOrEmpty(guid))
  150. {
  151. isNotAddressable = AssetReferenceDrawerUtilities.CheckForNewEntry(ref m_AssetName, aaSettings, guid, checkToForceAddressable);
  152. }
  153. return isNotAddressable;
  154. }
  155. private void DrawControl(SerializedProperty property, string nameToUse, bool isNotAddressable, string guid)
  156. {
  157. float pickerWidth = 20f;
  158. Rect pickerRect = assetDropDownRect;
  159. pickerRect.width = pickerWidth;
  160. pickerRect.x = assetDropDownRect.xMax - pickerWidth;
  161. bool isPickerPressed = Event.current.type == EventType.MouseDown && Event.current.button == 0 && pickerRect.Contains(Event.current.mousePosition);
  162. bool isEnterKeyPressed = Event.current.type == EventType.KeyDown && Event.current.isKey && (Event.current.keyCode == KeyCode.KeypadEnter || Event.current.keyCode == KeyCode.Return);
  163. var asset = m_AssetRefObject?.editorAsset;
  164. if (asset != null && m_ReferencesSame)
  165. {
  166. float iconHeight = EditorGUIUtility.singleLineHeight - EditorGUIUtility.standardVerticalSpacing * 3;
  167. Vector2 iconSize = EditorGUIUtility.GetIconSize();
  168. EditorGUIUtility.SetIconSize(new Vector2(iconHeight, iconHeight));
  169. string assetPath = AssetDatabase.GUIDToAssetPath(m_AssetRefObject.AssetGUID);
  170. Texture2D assetIcon = AssetDatabase.GetCachedIcon(assetPath) as Texture2D;
  171. UnityEngine.GUI.SetNextControlName(k_FieldControlName);
  172. if (EditorGUI.DropdownButton(assetDropDownRect, new GUIContent(nameToUse, assetIcon), FocusType.Keyboard, EditorStyles.objectField))
  173. {
  174. if (Event.current.clickCount == 1)
  175. {
  176. UnityEngine.GUI.FocusControl(k_FieldControlName);
  177. EditorGUIUtility.PingObject(asset);
  178. }
  179. if (Event.current.clickCount == 2)
  180. {
  181. AssetDatabase.OpenAsset(asset);
  182. GUIUtility.ExitGUI();
  183. }
  184. }
  185. EditorGUIUtility.SetIconSize(iconSize);
  186. }
  187. else
  188. {
  189. UnityEngine.GUI.SetNextControlName(k_FieldControlName);
  190. if (EditorGUI.DropdownButton(assetDropDownRect, new GUIContent(nameToUse), FocusType.Keyboard, EditorStyles.objectField))
  191. UnityEngine.GUI.FocusControl(k_FieldControlName);
  192. }
  193. DrawCaret(pickerRect);
  194. bool enterKeyRequestsPopup = isEnterKeyPressed && (k_FieldControlName == UnityEngine.GUI.GetNameOfFocusedControl());
  195. if (isPickerPressed || enterKeyRequestsPopup)
  196. {
  197. newGuidPropertyPath = property.propertyPath;
  198. var nonAddressedOption = isNotAddressable ? m_AssetName : string.Empty;
  199. PopupWindow.Show(assetDropDownRect, new AssetReferencePopup(this, guid, nonAddressedOption, enterKeyRequestsPopup));
  200. }
  201. }
  202. private void DrawCaret(Rect pickerRect)
  203. {
  204. if (m_CaretTexture == null)
  205. {
  206. string caretIconPath = EditorGUIUtility.isProSkin
  207. ? @"Packages\com.unity.addressables\Editor\Icons\PickerDropArrow-Pro.png"
  208. : @"Packages\com.unity.addressables\Editor\Icons\PickerDropArrow-Personal.png";
  209. if (File.Exists(caretIconPath))
  210. {
  211. m_CaretTexture = (Texture2D)AssetDatabase.LoadAssetAtPath(caretIconPath, typeof(Texture2D));
  212. }
  213. }
  214. if (m_CaretTexture != null)
  215. {
  216. UnityEngine.GUI.DrawTexture(pickerRect, m_CaretTexture, ScaleMode.ScaleToFit);
  217. }
  218. }
  219. private void HandleDragAndDrop(SerializedProperty property, bool isDragging, bool isDropping, string guid)
  220. {
  221. var aaSettings = AddressableAssetSettingsDefaultObject.Settings;
  222. //During the drag, doing a light check on asset validity. The in-depth check happens during a drop, and should include a log if it fails.
  223. var rejectedDrag = false;
  224. if (isDragging)
  225. {
  226. if (aaSettings == null)
  227. rejectedDrag = true;
  228. else
  229. {
  230. var aaEntries = DragAndDrop.GetGenericData("AssetEntryTreeViewItem") as List<AssetEntryTreeViewItem>;
  231. rejectedDrag = AssetReferenceDrawerUtilities.ValidateDrag(m_AssetRefObject, Restrictions, aaEntries, DragAndDrop.objectReferences, DragAndDrop.paths);
  232. }
  233. DragAndDrop.visualMode = rejectedDrag ? DragAndDropVisualMode.Rejected : DragAndDropVisualMode.Copy;
  234. }
  235. if (!rejectedDrag && isDropping)
  236. {
  237. var aaEntries = DragAndDrop.GetGenericData("AssetEntryTreeViewItem") as List<AssetEntryTreeViewItem>;
  238. if (aaEntries != null)
  239. {
  240. if (aaEntries.Count == 1)
  241. {
  242. var item = aaEntries[0];
  243. if (item.entry != null)
  244. {
  245. if (item.entry.IsInResources)
  246. Addressables.LogWarning("Cannot use an AssetReference on an asset in Resources. Move asset out of Resources first.");
  247. else
  248. {
  249. if (AssetReferenceDrawerUtilities.SetObject(ref m_AssetRefObject, ref m_ReferencesSame, property, item.entry.TargetAsset, fieldInfo, m_label.text, out guid))
  250. TriggerOnValidate(property);
  251. }
  252. }
  253. }
  254. }
  255. else
  256. {
  257. if (DragAndDrop.paths != null && DragAndDrop.paths.Length == 1)
  258. {
  259. var path = DragAndDrop.paths[0];
  260. DragAndDropNotFromAddressableGroupWindow(path, guid, property, aaSettings);
  261. }
  262. }
  263. }
  264. }
  265. internal void DragAndDropNotFromAddressableGroupWindow(string path, string guid, SerializedProperty property, AddressableAssetSettings aaSettings)
  266. {
  267. if (AddressableAssetUtility.IsInResources(path))
  268. Addressables.LogWarning("Cannot use an AssetReference on an asset in Resources. Move asset out of Resources first. ");
  269. else if (!AddressableAssetUtility.IsPathValidForEntry(path))
  270. Addressables.LogWarning("Dragged asset is not valid as an Asset Reference. " + path);
  271. else
  272. {
  273. Object obj;
  274. if (DragAndDrop.objectReferences != null && DragAndDrop.objectReferences.Length == 1)
  275. obj = DragAndDrop.objectReferences[0];
  276. else
  277. obj = AssetDatabase.LoadAssetAtPath<Object>(path);
  278. if (AssetReferenceDrawerUtilities.SetObject(ref m_AssetRefObject, ref m_ReferencesSame, property, obj, fieldInfo, m_label.text, out guid))
  279. {
  280. TriggerOnValidate(property);
  281. aaSettings = AddressableAssetSettingsDefaultObject.GetSettings(true);
  282. var entry = aaSettings.FindAssetEntry(guid);
  283. if (entry == null && !string.IsNullOrEmpty(guid))
  284. {
  285. string assetName;
  286. if (!aaSettings.IsAssetPathInAddressableDirectory(path, out assetName))
  287. {
  288. aaSettings.CreateOrMoveEntry(guid, aaSettings.DefaultGroup);
  289. newGuid = guid;
  290. }
  291. }
  292. }
  293. }
  294. }
  295. private Rect DrawSubAssetsControl(SerializedProperty property, List<Object> subAssets)
  296. {
  297. assetDropDownRect = new Rect(assetDropDownRect.position, new Vector2(assetDropDownRect.width / 2, assetDropDownRect.height));
  298. var objRect = new Rect(assetDropDownRect.xMax, assetDropDownRect.y, assetDropDownRect.width, assetDropDownRect.height);
  299. float pickerWidth = 20f;
  300. Rect pickerRect = objRect;
  301. pickerRect.width = pickerWidth;
  302. pickerRect.x = objRect.xMax - pickerWidth;
  303. bool multipleSubassets = false;
  304. // Check if targetObjects have multiple different selected
  305. if (property.serializedObject.targetObjects.Length > 1)
  306. multipleSubassets = AssetReferenceDrawerUtilities.CheckTargetObjectsSubassetsAreDifferent(property, m_AssetRefObject.SubObjectName, fieldInfo, m_label.text);
  307. bool isPickerPressed = Event.current.type == EventType.MouseDown && Event.current.button == 0 && pickerRect.Contains(Event.current.mousePosition);
  308. if (isPickerPressed)
  309. {
  310. // Do custom popup with scroll to pick subasset
  311. if (m_SubassetPopup == null || m_SubassetPopup.m_property != property)
  312. {
  313. m_SubassetPopup = CreateSubAssetPopup(property, subAssets ?? AssetReferenceDrawerUtilities.GetSubAssetsList(m_AssetRefObject));
  314. }
  315. PopupWindow.Show(objRect, m_SubassetPopup);
  316. }
  317. if (m_SubassetPopup != null && m_SubassetPopup.SelectionChanged)
  318. {
  319. m_SubassetPopup.UpdateSubAssets();
  320. }
  321. // Show selected name
  322. GUIContent nameSelected = new GUIContent("--");
  323. if (!multipleSubassets)
  324. nameSelected.text = AssetReferenceDrawerUtilities.FormatName(m_AssetRefObject.SubObjectName);
  325. UnityEngine.GUI.Box(objRect, nameSelected, EditorStyles.objectField);
  326. // Draw picker arrow
  327. DrawCaret(pickerRect);
  328. return assetDropDownRect;
  329. }
  330. internal void GetSelectedSubassetIndex(List<Object> subAssets, out int selIndex, out string[] objNames)
  331. {
  332. var subAssetNames = subAssets.Select(sa => sa == null ? "<none>" : $"{AssetReferenceDrawerUtilities.FormatName(sa.name)}:{sa.GetType()}").ToList();
  333. objNames = subAssetNames.ToArray();
  334. selIndex = subAssetNames.IndexOf($"{m_AssetRefObject.SubObjectName}:{m_AssetRefObject.SubOjbectType}");
  335. if (selIndex == -1)
  336. selIndex = 0;
  337. }
  338. SubassetPopup CreateSubAssetPopup(SerializedProperty property, List<Object> subAssets)
  339. {
  340. GetSelectedSubassetIndex(subAssets, out int selIndex, out string[] objNames);
  341. return new SubassetPopup(selIndex, objNames, subAssets, property, this);
  342. }
  343. }
  344. class SubassetPopup : PopupWindowContent
  345. {
  346. internal int SelectedIndex = 0;
  347. internal SerializedProperty m_property;
  348. private string[] m_objNames;
  349. private List<Object> m_subAssets;
  350. private AssetReferenceDrawer m_drawer;
  351. private Vector2 m_scrollPosition;
  352. bool selectionChanged = false;
  353. internal SubassetPopup(int selIndex, string[] objNames, List<Object> subAssets, SerializedProperty property, AssetReferenceDrawer drawer)
  354. {
  355. SelectedIndex = selIndex;
  356. m_objNames = objNames;
  357. m_property = property;
  358. m_drawer = drawer;
  359. m_subAssets = subAssets;
  360. }
  361. public bool SelectionChanged => selectionChanged;
  362. public void UpdateSubAssets()
  363. {
  364. if (selectionChanged)
  365. {
  366. if (!AssetReferenceDrawerUtilities.SetSubAssets(m_property, m_subAssets[SelectedIndex], m_drawer.fieldInfo, m_drawer.m_label.text))
  367. {
  368. Debug.LogError("Unable to set all of the objects selected subassets");
  369. }
  370. else
  371. {
  372. m_property.serializedObject.ApplyModifiedProperties();
  373. m_property.serializedObject.Update();
  374. m_property.FindPropertyRelative("m_EditorAssetChanged").boolValue = false;
  375. }
  376. selectionChanged = false;
  377. }
  378. }
  379. public override void OnGUI(Rect rect)
  380. {
  381. var buttonStyle = new GUIStyle();
  382. buttonStyle.fontStyle = FontStyle.Normal;
  383. buttonStyle.fontSize = 12;
  384. buttonStyle.contentOffset = new Vector2(10, 0);
  385. buttonStyle.normal.textColor = Color.white;
  386. EditorGUILayout.BeginVertical();
  387. m_scrollPosition = EditorGUILayout.BeginScrollView(m_scrollPosition, GUILayout.Width(rect.width), GUILayout.Height(rect.height));
  388. for (int i = 0; i < m_objNames.Length; i++)
  389. {
  390. if (GUILayout.Button(m_objNames[i], buttonStyle))
  391. {
  392. if (SelectedIndex != i)
  393. {
  394. SelectedIndex = i;
  395. selectionChanged = true;
  396. }
  397. PopupWindow.focusedWindow.Close();
  398. break;
  399. }
  400. }
  401. EditorGUILayout.EndScrollView();
  402. EditorGUILayout.EndVertical();
  403. }
  404. }
  405. class AssetReferencePopup : PopupWindowContent
  406. {
  407. AssetReferenceTreeView m_Tree;
  408. TreeViewState m_TreeState;
  409. bool m_ShouldClose;
  410. void ForceClose()
  411. {
  412. m_ShouldClose = true;
  413. }
  414. string m_CurrentName = string.Empty;
  415. AssetReferenceDrawer m_Drawer;
  416. string m_GUID;
  417. string m_NonAddressedAsset;
  418. SearchField m_SearchField;
  419. bool m_OpenedByEnterKey;
  420. internal AssetReferencePopup(AssetReferenceDrawer drawer, string guid, string nonAddressedAsset, bool openedByEnterKey)
  421. {
  422. m_Drawer = drawer;
  423. m_GUID = guid;
  424. m_NonAddressedAsset = nonAddressedAsset;
  425. m_SearchField = new SearchField();
  426. m_ShouldClose = false;
  427. m_OpenedByEnterKey = openedByEnterKey;
  428. }
  429. public override void OnOpen()
  430. {
  431. m_SearchField.SetFocus();
  432. base.OnOpen();
  433. }
  434. public override Vector2 GetWindowSize()
  435. {
  436. Vector2 result = base.GetWindowSize();
  437. result.x = m_Drawer.assetDropDownRect.width;
  438. return result;
  439. }
  440. public override void OnGUI(Rect rect)
  441. {
  442. int border = 4;
  443. int topPadding = 12;
  444. int searchHeight = 20;
  445. var searchRect = new Rect(border, topPadding, rect.width - border * 2, searchHeight);
  446. var remainTop = topPadding + searchHeight + border;
  447. var remainingRect = new Rect(border, topPadding + searchHeight + border, rect.width - border * 2, rect.height - remainTop - border);
  448. bool isEnterKeyPressed = Event.current.type == EventType.KeyDown && Event.current.isKey && (Event.current.keyCode == KeyCode.KeypadEnter || Event.current.keyCode == KeyCode.Return);
  449. m_CurrentName = m_SearchField.OnGUI(searchRect, m_CurrentName);
  450. if (m_Tree == null)
  451. {
  452. if (m_TreeState == null)
  453. m_TreeState = new TreeViewState();
  454. m_Tree = new AssetReferenceTreeView(m_TreeState, m_Drawer, this, m_GUID, m_NonAddressedAsset);
  455. m_Tree.Reload();
  456. }
  457. m_Tree.searchString = m_CurrentName;
  458. m_Tree.OnGUI(remainingRect);
  459. if (m_ShouldClose || (isEnterKeyPressed && m_OpenedByEnterKey))
  460. {
  461. GUIUtility.hotControl = 0;
  462. editorWindow.Close();
  463. }
  464. }
  465. sealed class AssetRefTreeViewItem : TreeViewItem
  466. {
  467. public string AssetPath;
  468. private string m_Guid;
  469. public string Guid
  470. {
  471. get
  472. {
  473. if (string.IsNullOrEmpty(m_Guid))
  474. m_Guid = AssetDatabase.AssetPathToGUID(AssetPath);
  475. return m_Guid;
  476. }
  477. }
  478. public AssetRefTreeViewItem(int id, int depth, string displayName, string path)
  479. : base(id, depth, displayName)
  480. {
  481. AssetPath = path;
  482. icon = AssetDatabase.GetCachedIcon(path) as Texture2D;
  483. }
  484. }
  485. internal class AssetReferenceTreeView : TreeView
  486. {
  487. AssetReferenceDrawer m_Drawer;
  488. AssetReferencePopup m_Popup;
  489. string m_GUID;
  490. string m_NonAddressedAsset;
  491. Texture2D m_WarningIcon;
  492. public AssetReferenceTreeView(TreeViewState state, AssetReferenceDrawer drawer, AssetReferencePopup popup, string guid, string nonAddressedAsset)
  493. : base(state)
  494. {
  495. m_Drawer = drawer;
  496. m_Popup = popup;
  497. showBorder = true;
  498. showAlternatingRowBackgrounds = true;
  499. m_GUID = guid;
  500. m_NonAddressedAsset = nonAddressedAsset;
  501. m_WarningIcon = EditorGUIUtility.FindTexture("console.warnicon");
  502. }
  503. protected override bool CanMultiSelect(TreeViewItem item)
  504. {
  505. return false;
  506. }
  507. protected override void SelectionChanged(IList<int> selectedIds)
  508. {
  509. if (selectedIds != null && selectedIds.Count == 1)
  510. {
  511. var assetRefItem = FindItem(selectedIds[0], rootItem) as AssetRefTreeViewItem;
  512. if (assetRefItem != null && !string.IsNullOrEmpty(assetRefItem.AssetPath))
  513. {
  514. m_Drawer.newGuid = assetRefItem.Guid;
  515. if (string.IsNullOrEmpty(m_Drawer.newGuid))
  516. m_Drawer.newGuid = assetRefItem.AssetPath;
  517. }
  518. else
  519. {
  520. m_Drawer.newGuid = AssetReferenceDrawer.noAssetString;
  521. }
  522. m_Popup.ForceClose();
  523. }
  524. }
  525. protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
  526. {
  527. if (string.IsNullOrEmpty(searchString))
  528. {
  529. return base.BuildRows(root);
  530. }
  531. List<TreeViewItem> rows = new List<TreeViewItem>();
  532. foreach (var child in rootItem.children)
  533. {
  534. if (child.displayName.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0)
  535. rows.Add(child);
  536. }
  537. return rows;
  538. }
  539. protected override TreeViewItem BuildRoot()
  540. {
  541. var root = new TreeViewItem(-1, -1);
  542. var aaSettings = AddressableAssetSettingsDefaultObject.Settings;
  543. if (aaSettings == null)
  544. {
  545. var message = "Use 'Window->Addressables' to initialize.";
  546. root.AddChild(new AssetRefTreeViewItem(message.GetHashCode(), 0, message, string.Empty));
  547. }
  548. else
  549. {
  550. if (!string.IsNullOrEmpty(m_NonAddressedAsset))
  551. {
  552. var item = new AssetRefTreeViewItem(m_NonAddressedAsset.GetHashCode(), 0,
  553. AssetReferenceDrawer.forceAddressableString + m_NonAddressedAsset, AssetReferenceDrawer.forceAddressableString);
  554. item.icon = m_WarningIcon;
  555. root.AddChild(item);
  556. }
  557. root.AddChild(new AssetRefTreeViewItem(AssetReferenceDrawer.noAssetString.GetHashCode(), 0, AssetReferenceDrawer.noAssetString, string.Empty));
  558. var allAssets = new List<IReferenceEntryData>();
  559. aaSettings.GatherAllAssetReferenceDrawableEntries(allAssets);
  560. foreach (var entry in allAssets)
  561. {
  562. if (!entry.IsInResources &&
  563. m_Drawer.ValidateAsset(entry))
  564. {
  565. var child = new AssetRefTreeViewItem(entry.AssetPath.GetHashCode(), 0, entry.address, entry.AssetPath);
  566. root.AddChild(child);
  567. }
  568. }
  569. }
  570. return root;
  571. }
  572. }
  573. }
  574. /// <summary>
  575. /// Used to manipulate data from a serialized property.
  576. /// </summary>
  577. public static class SerializedPropertyExtensions
  578. {
  579. /// <summary>
  580. /// Used to extract the target object from a serialized property.
  581. /// </summary>
  582. /// <typeparam name="T">The type of the object to extract.</typeparam>
  583. /// <param name="property">The property containing the object.</param>
  584. /// <param name="field">The field data.</param>
  585. /// <param name="label">The label name.</param>
  586. /// <returns>Returns the target object type.</returns>
  587. public static T GetActualObjectForSerializedProperty<T>(this SerializedProperty property, FieldInfo field, ref string label)
  588. {
  589. try
  590. {
  591. if (property == null || field == null)
  592. return default(T);
  593. var serializedObject = property.serializedObject;
  594. if (serializedObject == null)
  595. {
  596. return default(T);
  597. }
  598. var targetObject = serializedObject.targetObject;
  599. if (property.depth > 0)
  600. {
  601. var slicedName = property.propertyPath.Split('.').ToList();
  602. List<int> arrayCounts = new List<int>();
  603. for (int index = 0; index < slicedName.Count; index++)
  604. {
  605. arrayCounts.Add(-1);
  606. var currName = slicedName[index];
  607. if (currName.EndsWith("]"))
  608. {
  609. var arraySlice = currName.Split('[', ']');
  610. if (arraySlice.Length >= 2)
  611. {
  612. arrayCounts[index - 2] = Convert.ToInt32(arraySlice[1]);
  613. slicedName[index] = string.Empty;
  614. slicedName[index - 1] = string.Empty;
  615. }
  616. }
  617. }
  618. while (string.IsNullOrEmpty(slicedName.Last()))
  619. {
  620. int i = slicedName.Count - 1;
  621. slicedName.RemoveAt(i);
  622. arrayCounts.RemoveAt(i);
  623. }
  624. if (property.propertyPath.EndsWith("]"))
  625. {
  626. var slice = property.propertyPath.Split('[', ']');
  627. if (slice.Length >= 2)
  628. label = "Element " + slice[slice.Length - 2];
  629. }
  630. return DescendHierarchy<T>(targetObject, slicedName, arrayCounts, 0);
  631. }
  632. var obj = field.GetValue(targetObject);
  633. return (T)obj;
  634. }
  635. catch
  636. {
  637. return default(T);
  638. }
  639. }
  640. static T DescendHierarchy<T>(object targetObject, List<string> splitName, List<int> splitCounts, int depth)
  641. {
  642. if (depth >= splitName.Count)
  643. return default(T);
  644. var currName = splitName[depth];
  645. if (string.IsNullOrEmpty(currName))
  646. return DescendHierarchy<T>(targetObject, splitName, splitCounts, depth + 1);
  647. int arrayIndex = splitCounts[depth];
  648. var newField = targetObject.GetType().GetField(currName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  649. if (newField == null)
  650. {
  651. Type baseType = targetObject.GetType().BaseType;
  652. while (baseType != null && newField == null)
  653. {
  654. newField = baseType.GetField(currName,
  655. BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  656. baseType = baseType.BaseType;
  657. }
  658. }
  659. var newObj = newField.GetValue(targetObject);
  660. if (depth == splitName.Count - 1)
  661. {
  662. T actualObject = default(T);
  663. if (arrayIndex >= 0)
  664. {
  665. if (newObj.GetType().IsArray && ((System.Array)newObj).Length > arrayIndex)
  666. actualObject = (T)((System.Array)newObj).GetValue(arrayIndex);
  667. var newObjList = newObj as IList;
  668. if (newObjList != null && newObjList.Count > arrayIndex)
  669. {
  670. actualObject = (T)newObjList[arrayIndex];
  671. //if (actualObject == null)
  672. // actualObject = new T();
  673. }
  674. }
  675. else
  676. {
  677. actualObject = (T)newObj;
  678. }
  679. return actualObject;
  680. }
  681. else if (arrayIndex >= 0)
  682. {
  683. if (newObj is IList)
  684. {
  685. IList list = (IList)newObj;
  686. newObj = list[arrayIndex];
  687. }
  688. else if (newObj is System.Array)
  689. {
  690. System.Array a = (System.Array)newObj;
  691. newObj = a.GetValue(arrayIndex);
  692. }
  693. }
  694. return DescendHierarchy<T>(newObj, splitName, splitCounts, depth + 1);
  695. }
  696. }
  697. /// <summary>
  698. /// Used to restrict a class to only allow items with specific labels.
  699. /// </summary>
  700. [AttributeUsage(AttributeTargets.Class)]
  701. public class AssetReferenceSurrogateAttribute : Attribute
  702. {
  703. /// <summary>
  704. /// The type of the attribute.
  705. /// </summary>
  706. public Type TargetType;
  707. /// <summary>
  708. /// Construct a new AssetReferenceSurrogateAttribute.
  709. /// </summary>
  710. /// <param name="type">The Type of the class in question.</param>
  711. public AssetReferenceSurrogateAttribute(Type type)
  712. {
  713. TargetType = type;
  714. }
  715. }
  716. /// <summary>
  717. /// Surrogate to AssetReferenceUIRestriction.
  718. /// This surrogate class provides the editor-side implementation of AssetReferenceUIRestriction attribute
  719. /// Used to restrict an AssetReference field or property to only allow items with specific labels. This is only enforced through the UI.
  720. /// </summary>
  721. [AssetReferenceSurrogate(typeof(AssetReferenceUIRestriction))]
  722. public class AssetReferenceUIRestrictionSurrogate : AssetReferenceUIRestriction
  723. {
  724. AssetReferenceUIRestriction data;
  725. /// <summary>
  726. /// Sets the AssetReferenceUIRestriction for this surrogate
  727. /// </summary>
  728. /// <param name="initData">To initialize AssetReferenceUIRestriction for surrogate</param>
  729. public virtual void Init(AssetReferenceUIRestriction initData)
  730. {
  731. data = initData;
  732. }
  733. /// <summary>
  734. /// Validates the referenced asset allowable for this asset reference.
  735. /// </summary>
  736. /// <param name="obj">The Object to validate.</param>
  737. /// <returns>Whether the referenced asset is valid.</returns>
  738. public override bool ValidateAsset(Object obj)
  739. {
  740. return data.ValidateAsset(obj);
  741. }
  742. internal virtual bool ValidateAsset(IReferenceEntryData entryData)
  743. {
  744. return data.ValidateAsset(entryData?.AssetPath);
  745. }
  746. }
  747. /// <summary>
  748. /// Surrogate to AssetReferenceUILabelRestriction
  749. /// This surrogate class provides the editor-side implementation of AssetReferenceUILabelRestriction attribute
  750. /// Used to restrict an AssetReference field or property to only allow items wil specific labels. This is only enforced through the UI.
  751. /// </summary>
  752. [AssetReferenceSurrogate(typeof(AssetReferenceUILabelRestriction))]
  753. public class AssetReferenceUILabelRestrictionSurrogate : AssetReferenceUIRestrictionSurrogate
  754. {
  755. AssetReferenceUILabelRestriction data;
  756. /// <summary>
  757. /// Sets the AssetReferenceUILabelRestriction for this surrogate
  758. /// </summary>
  759. /// <param name="initData">To initialize AssetReferenceUILabelRestriction field</param>
  760. public override void Init(AssetReferenceUIRestriction initData)
  761. {
  762. data = initData as AssetReferenceUILabelRestriction;
  763. }
  764. /// <inheritdoc/>
  765. public override bool ValidateAsset(Object obj)
  766. {
  767. var path = AssetDatabase.GetAssetOrScenePath(obj);
  768. return ValidateAsset(path);
  769. }
  770. /// <inheritdoc/>
  771. public override bool ValidateAsset(string path)
  772. {
  773. if (AddressableAssetSettingsDefaultObject.Settings == null)
  774. return false;
  775. var guid = AssetDatabase.AssetPathToGUID(path);
  776. var entry = AddressableAssetSettingsDefaultObject.Settings.FindAssetEntry(guid, true);
  777. return ValidateAsset(entry);
  778. }
  779. internal override bool ValidateAsset(IReferenceEntryData entry)
  780. {
  781. if (entry != null)
  782. {
  783. foreach (var label in data.m_AllowedLabels)
  784. {
  785. if (entry.labels.Contains(label))
  786. return true;
  787. }
  788. }
  789. return false;
  790. }
  791. ///<inheritdoc/>
  792. public override string ToString()
  793. {
  794. return data.ToString();
  795. }
  796. }
  797. /// <summary>
  798. /// Utility Class
  799. /// </summary>
  800. public class AssetReferenceUtility
  801. {
  802. /// <summary>
  803. /// Finds surrogate class for an Assembly with a particular TargetType
  804. /// </summary>
  805. /// <param name="targetType">Target Type to search</param>
  806. /// <returns>Type of the surrogate found for the Assembly with a particular Target Type.</returns>
  807. public static Type GetSurrogate(Type targetType)
  808. {
  809. if (targetType == null)
  810. {
  811. Debug.LogError("targetType cannot be null");
  812. return null;
  813. }
  814. Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
  815. List<Type> typesList = new List<Type>();
  816. foreach (Assembly assem in assemblies)
  817. {
  818. var assemblyTypeList = GatherTargetTypesFromAssembly(assem, targetType, out bool concreteTypeFound);
  819. if (concreteTypeFound == true)
  820. return assemblyTypeList[0];
  821. typesList.AddRange(assemblyTypeList);
  822. }
  823. if (typesList.Count == 0)
  824. return null;
  825. typesList.Sort(AssetReferenceUtility.CompareTypes);
  826. return typesList[0];
  827. }
  828. static int CompareTypes(object x, object y)
  829. {
  830. Type t1 = (Type)x;
  831. Type t2 = (Type)y;
  832. if (t1 == t2)
  833. return 0;
  834. else if (t1.IsAssignableFrom(t2))
  835. return 1;
  836. else
  837. return -1;
  838. }
  839. private static List<Type> GatherTargetTypesFromAssembly(Assembly assembly, Type targetType, out bool concreteTypeFound)
  840. {
  841. List<Type> assignableTypesList = new List<Type>();
  842. var typeList = assembly.GetTypes().Where(attrType => typeof(AssetReferenceUIRestrictionSurrogate).IsAssignableFrom(attrType)).ToList();
  843. foreach (var type in typeList)
  844. {
  845. var customAttribute = type.GetCustomAttribute<AssetReferenceSurrogateAttribute>();
  846. if (customAttribute == null)
  847. continue;
  848. if (customAttribute.TargetType == targetType)
  849. {
  850. assignableTypesList.Clear();
  851. assignableTypesList.Add(type);
  852. concreteTypeFound = true;
  853. return assignableTypesList;
  854. }
  855. if (customAttribute.TargetType.IsAssignableFrom(targetType))
  856. {
  857. assignableTypesList.Add(type);
  858. }
  859. }
  860. concreteTypeFound = false;
  861. return assignableTypesList;
  862. }
  863. }
  864. }