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