mathe/Library/PackageCache/com.unity.shadergraph@14.0.8/Editor/Drawing/SearchWindowProvider.cs
2024-09-20 20:30:10 +02:00

494 lines
20 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEditor.Graphing;
using UnityEditor.Graphing.Util;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEditor.Experimental.GraphView;
using UnityEngine.UIElements;
using UnityEditor.Searcher;
using UnityEngine.Profiling;
using UnityEngine.Pool;
namespace UnityEditor.ShaderGraph.Drawing
{
internal struct NodeEntry
{
public string[] title;
public AbstractMaterialNode node;
public int compatibleSlotId;
public string slotName;
}
class SearchWindowProvider : IDisposable
{
internal EditorWindow m_EditorWindow;
internal GraphData m_Graph;
internal GraphView m_GraphView;
internal Texture2D m_Icon;
public List<NodeEntry> currentNodeEntries;
public ShaderPort connectedPort { get; set; }
public bool nodeNeedsRepositioning { get; set; }
public SlotReference targetSlotReference { get; internal set; }
public Vector2 targetPosition { get; internal set; }
public VisualElement target { get; internal set; }
public bool regenerateEntries { get; set; }
private const string k_HiddenFolderName = "Hidden";
ShaderStageCapability m_ConnectedSlotCapability; // calculated in GenerateNodeEntries
public void Initialize(EditorWindow editorWindow, GraphData graph, GraphView graphView)
{
m_EditorWindow = editorWindow;
m_Graph = graph;
m_GraphView = graphView;
GenerateNodeEntries();
// Transparent icon to trick search window into indenting items
m_Icon = new Texture2D(1, 1);
m_Icon.SetPixel(0, 0, new Color(0, 0, 0, 0));
m_Icon.Apply();
}
public void Dispose()
{
if (m_Icon != null)
{
UnityEngine.Object.DestroyImmediate(m_Icon);
m_Icon = null;
}
m_EditorWindow = null;
m_Graph = null;
m_GraphView = null;
connectedPort = null;
currentNodeEntries?.Clear();
currentNodeEntries = null;
}
List<int> m_Ids;
List<MaterialSlot> m_Slots = new List<MaterialSlot>();
public void GenerateNodeEntries()
{
Profiler.BeginSample("SearchWindowProvider.GenerateNodeEntries");
// 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.
List<NodeEntry> nodeEntries = new List<NodeEntry>();
bool hideCustomInterpolators = m_Graph.activeTargets.All(at => at.ignoreCustomInterpolators);
if (connectedPort != null)
{
var slot = connectedPort.slot;
// Precalculate slot compatibility to avoid traversing graph for every added entry.
m_ConnectedSlotCapability = slot.stageCapability;
if (m_ConnectedSlotCapability == ShaderStageCapability.All || slot.owner is SubGraphNode)
{
m_ConnectedSlotCapability = NodeUtils.GetEffectiveShaderStageCapability(slot, true)
& NodeUtils.GetEffectiveShaderStageCapability(slot, false);
}
}
else
{
m_ConnectedSlotCapability = ShaderStageCapability.All;
}
if (target is ContextView contextView)
{
// Iterate all BlockFieldDescriptors currently cached on GraphData
foreach (var field in m_Graph.blockFieldDescriptors)
{
if (field.isHidden)
continue;
// Test stage
if (field.shaderStage != contextView.contextData.shaderStage)
continue;
// Create title
List<string> title = ListPool<string>.Get();
if (!string.IsNullOrEmpty(field.path))
{
var path = field.path.Split('/').ToList();
title.AddRange(path);
}
title.Add(field.displayName);
// Create and initialize BlockNode instance then add entry
var node = (BlockNode)Activator.CreateInstance(typeof(BlockNode));
node.Init(field);
AddEntries(node, title.ToArray(), nodeEntries);
}
SortEntries(nodeEntries);
if (contextView.contextData.shaderStage == ShaderStage.Vertex && !hideCustomInterpolators)
{
var customBlockNodeStub = (BlockNode)Activator.CreateInstance(typeof(BlockNode));
customBlockNodeStub.InitCustomDefault();
AddEntries(customBlockNodeStub, new string[] { "Custom Interpolator" }, nodeEntries);
}
currentNodeEntries = nodeEntries;
return;
}
Profiler.BeginSample("SearchWindowProvider.GenerateNodeEntries.IterateKnowNodes");
foreach (var type in NodeClassCache.knownNodeTypes)
{
if ((!type.IsClass || type.IsAbstract)
|| type == typeof(PropertyNode)
|| type == typeof(KeywordNode)
|| type == typeof(DropdownNode)
|| type == typeof(SubGraphNode))
continue;
TitleAttribute titleAttribute = NodeClassCache.GetAttributeOnNodeType<TitleAttribute>(type);
if (titleAttribute != null)
{
var node = (AbstractMaterialNode)Activator.CreateInstance(type);
if (!node.ExposeToSearcher)
continue;
if (ShaderGraphPreferences.allowDeprecatedBehaviors && node.latestVersion > 0)
{
var versions = node.allowedNodeVersions ?? Enumerable.Range(0, node.latestVersion + 1);
bool multiple = (versions.Count() > 1);
foreach (int i in versions)
{
var depNode = (AbstractMaterialNode)Activator.CreateInstance(type);
depNode.ChangeVersion(i);
if (multiple)
AddEntries(depNode, titleAttribute.title.Append($"v{i}").ToArray(), nodeEntries);
else
AddEntries(depNode, titleAttribute.title, nodeEntries);
}
}
else
{
AddEntries(node, titleAttribute.title, nodeEntries);
}
}
}
Profiler.EndSample();
Profiler.BeginSample("SearchWindowProvider.GenerateNodeEntries.IterateSubgraphAssets");
foreach (var asset in NodeClassCache.knownSubGraphAssets)
{
if (asset == null)
continue;
var node = new SubGraphNode { asset = asset };
var title = asset.path.Split('/').ToList();
if (asset.descendents.Contains(m_Graph.assetGuid) || asset.assetGuid == m_Graph.assetGuid)
{
continue;
}
if (string.IsNullOrEmpty(asset.path))
{
AddEntries(node, new string[1] { asset.name }, nodeEntries);
}
else if (title[0] != k_HiddenFolderName)
{
title.Add(asset.name);
AddEntries(node, title.ToArray(), nodeEntries);
}
}
Profiler.EndSample();
Profiler.BeginSample("SearchWindowProvider.GenerateNodeEntries.IterateGraphInputs");
foreach (var property in m_Graph.properties)
{
if (property is Serialization.MultiJsonInternal.UnknownShaderPropertyType)
continue;
var node = new PropertyNode();
node.property = property;
AddEntries(node, new[] { "Properties", "Property: " + property.displayName }, nodeEntries);
}
foreach (var keyword in m_Graph.keywords)
{
var node = new KeywordNode();
node.keyword = keyword;
AddEntries(node, new[] { "Keywords", "Keyword: " + keyword.displayName }, nodeEntries);
}
foreach (var dropdown in m_Graph.dropdowns)
{
var node = new DropdownNode();
node.dropdown = dropdown;
AddEntries(node, new[] { "Dropdowns", "dropdown: " + dropdown.displayName }, nodeEntries);
}
if (!hideCustomInterpolators)
{
foreach (var cibnode in m_Graph.vertexContext.blocks.Where(b => b.value.isCustomBlock))
{
var node = Activator.CreateInstance<CustomInterpolatorNode>();
node.ConnectToCustomBlock(cibnode.value);
AddEntries(node, new[] { "Custom Interpolator", cibnode.value.customName }, nodeEntries);
}
}
Profiler.EndSample();
SortEntries(nodeEntries);
currentNodeEntries = nodeEntries;
Profiler.EndSample();
}
void SortEntries(List<NodeEntry> nodeEntries)
{
// Sort the entries lexicographically by group then title with the requirement that items always comes before sub-groups in the same group.
// Example result:
// - Art/BlendMode
// - Art/Adjustments/ColorBalance
// - Art/Adjustments/Contrast
nodeEntries.Sort((entry1, entry2) =>
{
for (var i = 0; i < entry1.title.Length; i++)
{
if (i >= entry2.title.Length)
return 1;
var value = entry1.title[i].CompareTo(entry2.title[i]);
if (value != 0)
{
// Make sure that leaves go before nodes
if (entry1.title.Length != entry2.title.Length && (i == entry1.title.Length - 1 || i == entry2.title.Length - 1))
{
//once nodes are sorted, sort slot entries by slot order instead of alphebetically
var alphaOrder = entry1.title.Length < entry2.title.Length ? -1 : 1;
var slotOrder = entry1.compatibleSlotId.CompareTo(entry2.compatibleSlotId);
return alphaOrder.CompareTo(slotOrder);
}
return value;
}
}
return 0;
});
}
void AddEntries(AbstractMaterialNode node, string[] title, List<NodeEntry> addNodeEntries)
{
if (m_Graph.isSubGraph && !node.allowedInSubGraph)
return;
if (!m_Graph.isSubGraph && !node.allowedInMainGraph)
return;
if (connectedPort == null)
{
addNodeEntries.Add(new NodeEntry
{
node = node,
title = title,
compatibleSlotId = -1
});
return;
}
var connectedSlot = connectedPort.slot;
m_Slots.Clear();
node.GetSlots(m_Slots);
foreach (var slot in m_Slots)
{
if (!slot.IsCompatibleWith(connectedSlot))
{
continue;
}
if (!slot.IsCompatibleStageWith(m_ConnectedSlotCapability))
{
continue;
}
//var entryTitle = new string[title.Length];
//title.CopyTo(entryTitle, 0);
//entryTitle[entryTitle.Length - 1] += ": " + slot.displayName;
addNodeEntries.Add(new NodeEntry
{
title = title,
node = node,
compatibleSlotId = slot.id,
slotName = slot.displayName
});
}
}
}
class SearcherProvider : SearchWindowProvider
{
public Searcher.Searcher LoadSearchWindow()
{
if (regenerateEntries)
{
GenerateNodeEntries();
regenerateEntries = false;
}
//create empty root for searcher tree
var root = new List<SearcherItem>();
var dummyEntry = new NodeEntry();
foreach (var nodeEntry in currentNodeEntries)
{
SearcherItem item = null;
SearcherItem parent = null;
for (int i = 0; i < nodeEntry.title.Length; i++)
{
var pathEntry = nodeEntry.title[i];
List<SearcherItem> children = parent != null ? parent.Children : root;
item = children.Find(x => x.Name == pathEntry);
if (item == null)
{
//if we have slot entries and are at a leaf, add the slot name to the entry title
if (nodeEntry.compatibleSlotId != -1 && i == nodeEntry.title.Length - 1)
item = new SearchNodeItem(pathEntry + ": " + nodeEntry.slotName, nodeEntry, nodeEntry.node.synonyms);
//if we don't have slot entries and are at a leaf, add userdata to the entry
else if (nodeEntry.compatibleSlotId == -1 && i == nodeEntry.title.Length - 1)
item = new SearchNodeItem(pathEntry, nodeEntry, nodeEntry.node.synonyms);
//if we aren't a leaf, don't add user data
else
item = new SearchNodeItem(pathEntry, dummyEntry, null);
if (parent != null)
{
parent.AddChild(item);
}
else
{
children.Add(item);
}
}
parent = item;
if (parent.Depth == 0 && !root.Contains(parent))
root.Add(parent);
}
}
var nodeDatabase = SearcherDatabase.Create(root, string.Empty, false);
return new Searcher.Searcher(nodeDatabase, new SearchWindowAdapter("Create Node"));
}
public bool OnSearcherSelectEntry(SearcherItem entry, Vector2 screenMousePosition)
{
if (entry == null || (entry as SearchNodeItem).NodeGUID.node == null)
return true;
var nodeEntry = (entry as SearchNodeItem).NodeGUID;
if (nodeEntry.node is PropertyNode propNode)
if (propNode.property is Serialization.MultiJsonInternal.UnknownShaderPropertyType)
return true;
var node = CopyNodeForGraph(nodeEntry.node);
var windowRoot = m_EditorWindow.rootVisualElement;
var windowMousePosition = windowRoot.ChangeCoordinatesTo(windowRoot.parent, screenMousePosition); //- m_EditorWindow.position.position);
var graphMousePosition = m_GraphView.contentViewContainer.WorldToLocal(windowMousePosition);
m_Graph.owner.RegisterCompleteObjectUndo("Add " + node.name);
if (node is BlockNode blockNode)
{
if (!(target is ContextView contextView))
return true;
// ensure custom blocks have a unique name provided the existing context.
if (blockNode.isCustomBlock)
{
HashSet<string> usedNames = new HashSet<string>();
foreach (var other in contextView.contextData.blocks) usedNames.Add(other.value.descriptor.displayName);
blockNode.customName = GraphUtil.SanitizeName(usedNames, "{0}_{1}", blockNode.descriptor.displayName);
}
// Test against all current BlockNodes in the Context
// Never allow duplicate BlockNodes
else if (contextView.contextData.blocks.Where(x => x.value.name == blockNode.name).FirstOrDefault().value != null)
{
return true;
}
// Insert block to Data
blockNode.owner = m_Graph;
int index = contextView.GetInsertionIndex(screenMousePosition);
m_Graph.AddBlock(blockNode, contextView.contextData, index);
return true;
}
var drawState = node.drawState;
drawState.position = new Rect(graphMousePosition, Vector2.zero);
node.drawState = drawState;
m_Graph.AddNode(node);
if (connectedPort != null)
{
var connectedSlot = connectedPort.slot;
var connectedSlotReference = connectedSlot.owner.GetSlotReference(connectedSlot.id);
var compatibleSlotReference = node.GetSlotReference(nodeEntry.compatibleSlotId);
var fromReference = connectedSlot.isOutputSlot ? connectedSlotReference : compatibleSlotReference;
var toReference = connectedSlot.isOutputSlot ? compatibleSlotReference : connectedSlotReference;
m_Graph.Connect(fromReference, toReference);
nodeNeedsRepositioning = true;
targetSlotReference = compatibleSlotReference;
targetPosition = graphMousePosition;
}
return true;
}
public AbstractMaterialNode CopyNodeForGraph(AbstractMaterialNode oldNode)
{
var newNode = (AbstractMaterialNode)Activator.CreateInstance(oldNode.GetType());
if (ShaderGraphPreferences.allowDeprecatedBehaviors && oldNode.sgVersion != newNode.sgVersion)
{
newNode.ChangeVersion(oldNode.sgVersion);
}
if (newNode is SubGraphNode subgraphNode)
{
subgraphNode.asset = ((SubGraphNode)oldNode).asset;
}
else if (newNode is PropertyNode propertyNode)
{
propertyNode.owner = m_Graph;
propertyNode.property = ((PropertyNode)oldNode).property;
propertyNode.owner = null;
}
else if (newNode is KeywordNode keywordNode)
{
keywordNode.owner = m_Graph;
keywordNode.keyword = ((KeywordNode)oldNode).keyword;
keywordNode.owner = null;
}
else if (newNode is DropdownNode dropdownNode)
{
dropdownNode.owner = m_Graph;
dropdownNode.dropdown = ((DropdownNode)oldNode).dropdown;
dropdownNode.owner = null;
}
else if (newNode is BlockNode blockNode)
{
blockNode.owner = m_Graph;
blockNode.Init(((BlockNode)oldNode).descriptor);
blockNode.owner = null;
}
else if (newNode is CustomInterpolatorNode cinode)
{
cinode.owner = m_Graph;
cinode.ConnectToCustomBlockByName(((CustomInterpolatorNode)oldNode).customBlockNodeName);
cinode.owner = null;
}
return newNode;
}
}
}