Bez popisu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SearchWindowProvider.cs 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using UnityEditor.Graphing;
  6. using UnityEditor.Graphing.Util;
  7. using UnityEngine;
  8. using UnityEditor.UIElements;
  9. using UnityEditor.Experimental.GraphView;
  10. using UnityEngine.UIElements;
  11. using UnityEditor.Searcher;
  12. using UnityEngine.Profiling;
  13. using UnityEngine.Pool;
  14. using Object = UnityEngine.Object;
  15. namespace UnityEditor.ShaderGraph.Drawing
  16. {
  17. internal struct NodeEntry
  18. {
  19. public string[] title;
  20. public AbstractMaterialNode node;
  21. public int compatibleSlotId;
  22. public string slotName;
  23. }
  24. class SearchWindowProvider : IDisposable
  25. {
  26. internal EditorWindow m_EditorWindow;
  27. internal GraphData m_Graph;
  28. internal GraphView m_GraphView;
  29. internal Texture2D m_Icon;
  30. public List<NodeEntry> currentNodeEntries;
  31. public ShaderPort connectedPort { get; set; }
  32. public bool nodeNeedsRepositioning { get; set; }
  33. public SlotReference targetSlotReference { get; internal set; }
  34. public Vector2 targetPosition { get; internal set; }
  35. public VisualElement target { get; internal set; }
  36. public bool regenerateEntries { get; set; }
  37. private const string k_HiddenFolderName = "Hidden";
  38. ShaderStageCapability m_ConnectedSlotCapability; // calculated in GenerateNodeEntries
  39. public void Initialize(EditorWindow editorWindow, GraphData graph, GraphView graphView)
  40. {
  41. m_EditorWindow = editorWindow;
  42. m_Graph = graph;
  43. m_GraphView = graphView;
  44. GenerateNodeEntries();
  45. // Transparent icon to trick search window into indenting items
  46. m_Icon = new Texture2D(1, 1);
  47. m_Icon.SetPixel(0, 0, new Color(0, 0, 0, 0));
  48. m_Icon.Apply();
  49. }
  50. public void Dispose()
  51. {
  52. if (m_Icon != null)
  53. {
  54. Object.DestroyImmediate(m_Icon);
  55. m_Icon = null;
  56. }
  57. m_EditorWindow = null;
  58. m_Graph = null;
  59. m_GraphView = null;
  60. connectedPort = null;
  61. currentNodeEntries?.Clear();
  62. currentNodeEntries = null;
  63. }
  64. List<int> m_Ids;
  65. List<MaterialSlot> m_Slots = new List<MaterialSlot>();
  66. public void GenerateNodeEntries()
  67. {
  68. Profiler.BeginSample("SearchWindowProvider.GenerateNodeEntries");
  69. // First build up temporary data structure containing group & title as an array of strings (the last one is the actual title) and associated node type.
  70. List<NodeEntry> nodeEntries = new List<NodeEntry>();
  71. bool hideCustomInterpolators = m_Graph.activeTargets.All(at => at.ignoreCustomInterpolators);
  72. if (connectedPort != null)
  73. {
  74. var slot = connectedPort.slot;
  75. // Precalculate slot compatibility to avoid traversing graph for every added entry.
  76. m_ConnectedSlotCapability = slot.stageCapability;
  77. if (m_ConnectedSlotCapability == ShaderStageCapability.All || slot.owner is SubGraphNode)
  78. {
  79. m_ConnectedSlotCapability = NodeUtils.GetEffectiveShaderStageCapability(slot, true)
  80. & NodeUtils.GetEffectiveShaderStageCapability(slot, false);
  81. }
  82. }
  83. else
  84. {
  85. m_ConnectedSlotCapability = ShaderStageCapability.All;
  86. }
  87. if (target is ContextView contextView)
  88. {
  89. // Iterate all BlockFieldDescriptors currently cached on GraphData
  90. foreach (var field in m_Graph.blockFieldDescriptors)
  91. {
  92. if (field.isHidden)
  93. continue;
  94. // Test stage
  95. if (field.shaderStage != contextView.contextData.shaderStage)
  96. continue;
  97. // Create title
  98. List<string> title = ListPool<string>.Get();
  99. if (!string.IsNullOrEmpty(field.path))
  100. {
  101. var path = field.path.Split('/').ToList();
  102. title.AddRange(path);
  103. }
  104. title.Add(field.displayName);
  105. // Create and initialize BlockNode instance then add entry
  106. var node = (BlockNode)Activator.CreateInstance(typeof(BlockNode));
  107. node.Init(field);
  108. AddEntries(node, title.ToArray(), nodeEntries);
  109. }
  110. SortEntries(nodeEntries);
  111. if (contextView.contextData.shaderStage == ShaderStage.Vertex && !hideCustomInterpolators)
  112. {
  113. var customBlockNodeStub = (BlockNode)Activator.CreateInstance(typeof(BlockNode));
  114. customBlockNodeStub.InitCustomDefault();
  115. AddEntries(customBlockNodeStub, new string[] { "Custom Interpolator" }, nodeEntries);
  116. }
  117. currentNodeEntries = nodeEntries;
  118. return;
  119. }
  120. Profiler.BeginSample("SearchWindowProvider.GenerateNodeEntries.IterateKnowNodes");
  121. foreach (var type in NodeClassCache.knownNodeTypes)
  122. {
  123. if ((!type.IsClass || type.IsAbstract)
  124. || type == typeof(PropertyNode)
  125. || type == typeof(KeywordNode)
  126. || type == typeof(DropdownNode)
  127. || type == typeof(SubGraphNode))
  128. continue;
  129. TitleAttribute titleAttribute = NodeClassCache.GetAttributeOnNodeType<TitleAttribute>(type);
  130. if (titleAttribute != null)
  131. {
  132. var node = (AbstractMaterialNode)Activator.CreateInstance(type);
  133. if (!node.ExposeToSearcher)
  134. continue;
  135. if (ShaderGraphPreferences.allowDeprecatedBehaviors && node.latestVersion > 0)
  136. {
  137. var versions = node.allowedNodeVersions ?? Enumerable.Range(0, node.latestVersion + 1);
  138. bool multiple = (versions.Count() > 1);
  139. foreach (int i in versions)
  140. {
  141. var depNode = (AbstractMaterialNode)Activator.CreateInstance(type);
  142. depNode.ChangeVersion(i);
  143. if (multiple)
  144. AddEntries(depNode, titleAttribute.title.Append($"v{i}").ToArray(), nodeEntries);
  145. else
  146. AddEntries(depNode, titleAttribute.title, nodeEntries);
  147. }
  148. }
  149. else
  150. {
  151. AddEntries(node, titleAttribute.title, nodeEntries);
  152. }
  153. }
  154. }
  155. Profiler.EndSample();
  156. Profiler.BeginSample("SearchWindowProvider.GenerateNodeEntries.IterateSubgraphAssets");
  157. foreach (var asset in NodeClassCache.knownSubGraphAssets)
  158. {
  159. if (asset == null)
  160. continue;
  161. var node = new SubGraphNode { asset = asset };
  162. var title = asset.path.Split('/').ToList();
  163. if (asset.descendents.Contains(m_Graph.assetGuid) || asset.assetGuid == m_Graph.assetGuid)
  164. {
  165. continue;
  166. }
  167. if (string.IsNullOrEmpty(asset.path))
  168. {
  169. AddEntries(node, new string[1] { asset.name }, nodeEntries);
  170. }
  171. else if (title[0] != k_HiddenFolderName)
  172. {
  173. title.Add(asset.name);
  174. AddEntries(node, title.ToArray(), nodeEntries);
  175. }
  176. }
  177. Profiler.EndSample();
  178. Profiler.BeginSample("SearchWindowProvider.GenerateNodeEntries.IterateGraphInputs");
  179. foreach (var property in m_Graph.properties)
  180. {
  181. if (property is Serialization.MultiJsonInternal.UnknownShaderPropertyType)
  182. continue;
  183. var node = new PropertyNode();
  184. node.property = property;
  185. AddEntries(node, new[] { "Properties", "Property: " + property.displayName }, nodeEntries);
  186. }
  187. foreach (var keyword in m_Graph.keywords)
  188. {
  189. var node = new KeywordNode();
  190. node.keyword = keyword;
  191. AddEntries(node, new[] { "Keywords", "Keyword: " + keyword.displayName }, nodeEntries);
  192. }
  193. foreach (var dropdown in m_Graph.dropdowns)
  194. {
  195. var node = new DropdownNode();
  196. node.dropdown = dropdown;
  197. AddEntries(node, new[] { "Dropdowns", "dropdown: " + dropdown.displayName }, nodeEntries);
  198. }
  199. if (!hideCustomInterpolators)
  200. {
  201. foreach (var cibnode in m_Graph.vertexContext.blocks.Where(b => b.value.isCustomBlock))
  202. {
  203. var node = Activator.CreateInstance<CustomInterpolatorNode>();
  204. node.ConnectToCustomBlock(cibnode.value);
  205. AddEntries(node, new[] { "Custom Interpolator", cibnode.value.customName }, nodeEntries);
  206. }
  207. }
  208. Profiler.EndSample();
  209. SortEntries(nodeEntries);
  210. currentNodeEntries = nodeEntries;
  211. Profiler.EndSample();
  212. }
  213. void SortEntries(List<NodeEntry> nodeEntries)
  214. {
  215. // Sort the entries lexicographically by group then title with the requirement that items always comes before sub-groups in the same group.
  216. // Example result:
  217. // - Art/BlendMode
  218. // - Art/Adjustments/ColorBalance
  219. // - Art/Adjustments/Contrast
  220. nodeEntries.Sort((entry1, entry2) =>
  221. {
  222. for (var i = 0; i < entry1.title.Length; i++)
  223. {
  224. if (i >= entry2.title.Length)
  225. return 1;
  226. var value = entry1.title[i].CompareTo(entry2.title[i]);
  227. if (value != 0)
  228. {
  229. // Make sure that leaves go before nodes
  230. if (entry1.title.Length != entry2.title.Length && (i == entry1.title.Length - 1 || i == entry2.title.Length - 1))
  231. {
  232. //once nodes are sorted, sort slot entries by slot order instead of alphebetically
  233. var alphaOrder = entry1.title.Length < entry2.title.Length ? -1 : 1;
  234. var slotOrder = entry1.compatibleSlotId.CompareTo(entry2.compatibleSlotId);
  235. return alphaOrder.CompareTo(slotOrder);
  236. }
  237. return value;
  238. }
  239. }
  240. return 0;
  241. });
  242. }
  243. void AddEntries(AbstractMaterialNode node, string[] title, List<NodeEntry> addNodeEntries)
  244. {
  245. if (m_Graph.isSubGraph && !node.allowedInSubGraph)
  246. return;
  247. if (!m_Graph.isSubGraph && !node.allowedInMainGraph)
  248. return;
  249. if (connectedPort == null)
  250. {
  251. addNodeEntries.Add(new NodeEntry
  252. {
  253. node = node,
  254. title = title,
  255. compatibleSlotId = -1
  256. });
  257. return;
  258. }
  259. var connectedSlot = connectedPort.slot;
  260. m_Slots.Clear();
  261. node.GetSlots(m_Slots);
  262. foreach (var slot in m_Slots)
  263. {
  264. if (!slot.IsCompatibleWith(connectedSlot))
  265. {
  266. continue;
  267. }
  268. if (!slot.IsCompatibleStageWith(m_ConnectedSlotCapability))
  269. {
  270. continue;
  271. }
  272. //var entryTitle = new string[title.Length];
  273. //title.CopyTo(entryTitle, 0);
  274. //entryTitle[entryTitle.Length - 1] += ": " + slot.displayName;
  275. addNodeEntries.Add(new NodeEntry
  276. {
  277. title = title,
  278. node = node,
  279. compatibleSlotId = slot.id,
  280. slotName = slot.displayName
  281. });
  282. }
  283. }
  284. }
  285. class SearcherProvider : SearchWindowProvider
  286. {
  287. public Searcher.Searcher LoadSearchWindow()
  288. {
  289. if (regenerateEntries)
  290. {
  291. GenerateNodeEntries();
  292. regenerateEntries = false;
  293. }
  294. //create empty root for searcher tree
  295. var root = new List<SearcherItem>();
  296. var dummyEntry = new NodeEntry();
  297. foreach (var nodeEntry in currentNodeEntries)
  298. {
  299. SearcherItem item = null;
  300. SearcherItem parent = null;
  301. for (int i = 0; i < nodeEntry.title.Length; i++)
  302. {
  303. var pathEntry = nodeEntry.title[i];
  304. List<SearcherItem> children = parent != null ? parent.Children : root;
  305. item = children.Find(x => x.Name == pathEntry);
  306. if (item == null)
  307. {
  308. //if we have slot entries and are at a leaf, add the slot name to the entry title
  309. if (nodeEntry.compatibleSlotId != -1 && i == nodeEntry.title.Length - 1)
  310. item = new SearchNodeItem(pathEntry + ": " + nodeEntry.slotName, nodeEntry, nodeEntry.node.synonyms);
  311. //if we don't have slot entries and are at a leaf, add userdata to the entry
  312. else if (nodeEntry.compatibleSlotId == -1 && i == nodeEntry.title.Length - 1)
  313. item = new SearchNodeItem(pathEntry, nodeEntry, nodeEntry.node.synonyms);
  314. //if we aren't a leaf, don't add user data
  315. else
  316. item = new SearchNodeItem(pathEntry, dummyEntry, null);
  317. if (parent != null)
  318. {
  319. parent.AddChild(item);
  320. }
  321. else
  322. {
  323. children.Add(item);
  324. }
  325. }
  326. parent = item;
  327. if (parent.Depth == 0 && !root.Contains(parent))
  328. root.Add(parent);
  329. }
  330. }
  331. var nodeDatabase = SearcherDatabase.Create(root, string.Empty, false);
  332. return new Searcher.Searcher(nodeDatabase, new SearchWindowAdapter("Create Node"));
  333. }
  334. public bool OnSearcherSelectEntry(SearcherItem entry, Vector2 screenMousePosition)
  335. {
  336. if (entry == null || (entry as SearchNodeItem).NodeGUID.node == null)
  337. return true;
  338. var nodeEntry = (entry as SearchNodeItem).NodeGUID;
  339. if (nodeEntry.node is PropertyNode propNode)
  340. if (propNode.property is Serialization.MultiJsonInternal.UnknownShaderPropertyType)
  341. return true;
  342. var node = CopyNodeForGraph(nodeEntry.node);
  343. var windowRoot = m_EditorWindow.rootVisualElement;
  344. var windowMousePosition = windowRoot.ChangeCoordinatesTo(windowRoot.parent, screenMousePosition); //- m_EditorWindow.position.position);
  345. var graphMousePosition = m_GraphView.contentViewContainer.WorldToLocal(windowMousePosition);
  346. m_Graph.owner.RegisterCompleteObjectUndo("Add " + node.name);
  347. if (node is BlockNode blockNode)
  348. {
  349. if (!(target is ContextView contextView))
  350. return true;
  351. // ensure custom blocks have a unique name provided the existing context.
  352. if (blockNode.isCustomBlock)
  353. {
  354. HashSet<string> usedNames = new HashSet<string>();
  355. foreach (var other in contextView.contextData.blocks) usedNames.Add(other.value.descriptor.displayName);
  356. blockNode.customName = GraphUtil.SanitizeName(usedNames, "{0}_{1}", blockNode.descriptor.displayName);
  357. }
  358. // Test against all current BlockNodes in the Context
  359. // Never allow duplicate BlockNodes
  360. else if (contextView.contextData.blocks.Where(x => x.value.name == blockNode.name).FirstOrDefault().value != null)
  361. {
  362. return true;
  363. }
  364. // Insert block to Data
  365. blockNode.owner = m_Graph;
  366. int index = contextView.GetInsertionIndex(screenMousePosition);
  367. m_Graph.AddBlock(blockNode, contextView.contextData, index);
  368. return true;
  369. }
  370. var drawState = node.drawState;
  371. drawState.position = new Rect(graphMousePosition, Vector2.zero);
  372. node.drawState = drawState;
  373. m_Graph.AddNode(node);
  374. if (connectedPort != null)
  375. {
  376. var connectedSlot = connectedPort.slot;
  377. var connectedSlotReference = connectedSlot.owner.GetSlotReference(connectedSlot.id);
  378. var compatibleSlotReference = node.GetSlotReference(nodeEntry.compatibleSlotId);
  379. var fromReference = connectedSlot.isOutputSlot ? connectedSlotReference : compatibleSlotReference;
  380. var toReference = connectedSlot.isOutputSlot ? compatibleSlotReference : connectedSlotReference;
  381. m_Graph.Connect(fromReference, toReference);
  382. nodeNeedsRepositioning = true;
  383. targetSlotReference = compatibleSlotReference;
  384. targetPosition = graphMousePosition;
  385. }
  386. return true;
  387. }
  388. public AbstractMaterialNode CopyNodeForGraph(AbstractMaterialNode oldNode)
  389. {
  390. var newNode = (AbstractMaterialNode)Activator.CreateInstance(oldNode.GetType());
  391. if (ShaderGraphPreferences.allowDeprecatedBehaviors && oldNode.sgVersion != newNode.sgVersion)
  392. {
  393. newNode.ChangeVersion(oldNode.sgVersion);
  394. }
  395. if (newNode is SubGraphNode subgraphNode)
  396. {
  397. subgraphNode.asset = ((SubGraphNode)oldNode).asset;
  398. }
  399. else if (newNode is PropertyNode propertyNode)
  400. {
  401. propertyNode.owner = m_Graph;
  402. propertyNode.property = ((PropertyNode)oldNode).property;
  403. propertyNode.owner = null;
  404. }
  405. else if (newNode is KeywordNode keywordNode)
  406. {
  407. keywordNode.owner = m_Graph;
  408. keywordNode.keyword = ((KeywordNode)oldNode).keyword;
  409. keywordNode.owner = null;
  410. }
  411. else if (newNode is DropdownNode dropdownNode)
  412. {
  413. dropdownNode.owner = m_Graph;
  414. dropdownNode.dropdown = ((DropdownNode)oldNode).dropdown;
  415. dropdownNode.owner = null;
  416. }
  417. else if (newNode is BlockNode blockNode)
  418. {
  419. blockNode.owner = m_Graph;
  420. blockNode.Init(((BlockNode)oldNode).descriptor);
  421. blockNode.owner = null;
  422. }
  423. else if (newNode is CustomInterpolatorNode cinode)
  424. {
  425. cinode.owner = m_Graph;
  426. cinode.ConnectToCustomBlockByName(((CustomInterpolatorNode)oldNode).customBlockNodeName);
  427. cinode.owner = null;
  428. }
  429. return newNode;
  430. }
  431. }
  432. }