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.
 
 
 
 

298 lines
11 KiB

using System;
using System.Collections.Generic;
using UnityEditor.ShaderGraph.Drawing;
using UnityEditor.ShaderGraph.Drawing.Colors;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph
{
[CustomEditor(typeof(ShaderGraphHeatmapValues))]
[CoreRPHelpURL("index", "com.unity.shadergraph")]
class ShaderGraphHeatmapValuesEditor : Editor
{
const string k_TemplatePath = "Packages/com.unity.shadergraph/Editor/Resources/UXML/HeatmapValuesEditor.uxml";
const string k_StylePath = "Packages/com.unity.shadergraph/Editor/Resources/Styles/HeatmapValuesEditor.uss";
const string k_ColorsListName = "colors-list";
const string k_NodeListName = "nodes-list";
const string k_SubgraphListName = "subgraph-list";
const string k_NodeTitleColumnName = "node";
const string k_SubgraphColumnName = "subgraph";
const string k_HeatValueColumnName = "value";
const string k_HeatFieldUssClassName = "sg-heatmap__heat-field";
const string k_SubgraphPickerUssClassName = "sg-heatmap__subgraph-picker";
const string k_NodeLabelUssClassName = "sg-heatmap__node-label";
const string k_RefreshNodesHintName = "refresh-nodes-hint";
const string k_RefreshNodesButtonName = "refresh-nodes-button";
const string k_HelpBoxHiddenUssModifier = "sg-heatmap__help-box--hidden";
ShaderGraphHeatmapValues HeatmapValuesTarget => target as ShaderGraphHeatmapValues;
MultiColumnListView m_NodesListView;
MultiColumnListView m_SubgraphListView;
VisualElement m_RefreshNodesHint;
internal static void UpdateShaderGraphWindows()
{
foreach (var window in Resources.FindObjectsOfTypeAll<MaterialGraphEditWindow>())
{
var graphEditorView = window.graphEditorView;
if (graphEditorView == null)
{
continue;
}
var colorManager = graphEditorView.colorManager;
if (colorManager.activeProviderName != HeatmapColors.Title)
{
continue;
}
var nodeList = graphEditorView.Query<MaterialNodeView>().ToList();
colorManager.UpdateNodeViews(nodeList);
}
}
static Dictionary<string, string> s_NodeTypeNamesToTitles;
static Dictionary<string, string> NodeTypeNamesToTitles
{
get
{
if (s_NodeTypeNamesToTitles is null)
{
s_NodeTypeNamesToTitles = new Dictionary<string, string>();
foreach (var knownNodeType in NodeClassCache.knownNodeTypes)
{
var node = (AbstractMaterialNode) Activator.CreateInstance(knownNodeType);
s_NodeTypeNamesToTitles[knownNodeType.Name] = node.name;
}
}
return s_NodeTypeNamesToTitles;
}
}
static string GetTitleForNode(string nodeTypeName)
{
return string.IsNullOrEmpty(nodeTypeName)
? string.Empty
: NodeTypeNamesToTitles.GetValueOrDefault(nodeTypeName, nodeTypeName);
}
static void ConfigureNodeLabelColumn(Column column, HeatmapEntries entries)
{
column.makeCell = () =>
{
var ve = new Label();
ve.AddToClassList(k_NodeLabelUssClassName);
return ve;
};
column.bindCell = (v, i) =>
{
var label = (Label) v;
label.text = GetTitleForNode(entries.Entries[i].m_NodeName);
};
}
void ConfigureSubgraphPickerColumn(Column column)
{
var serializedEntries = serializedObject.FindProperty("m_Subgraphs");
var entries = HeatmapValuesTarget.Subgraphs;
column.makeCell = () =>
{
var ve = new ObjectField
{
tooltip = "Subgraph asset.",
objectType = typeof(SubGraphAsset),
allowSceneObjects = false,
};
ve.AddToClassList(k_SubgraphPickerUssClassName);
return ve;
};
column.bindCell = (v, i) =>
{
var objectField = (ObjectField) v;
objectField.RegisterCallback<ChangeEvent<UnityEngine.Object>, int>(OnSubgraphChanged, i);
if (string.IsNullOrEmpty(entries.Entries[i].m_NodeName))
{
return;
}
var assetPath = AssetDatabase.GUIDToAssetPath(entries.Entries[i].m_NodeName);
var asset = AssetDatabase.LoadAssetAtPath<SubGraphAsset>(assetPath);
objectField.SetValueWithoutNotify(asset);
};
column.unbindCell = (v, i) =>
{
var objectField = (ObjectField) v;
objectField.UnregisterCallback<ChangeEvent<UnityEngine.Object>, int>(OnSubgraphChanged);
};
return;
void OnSubgraphChanged(ChangeEvent<UnityEngine.Object> changeEvent, int index)
{
if (changeEvent.newValue is not SubGraphAsset subgraph)
{
return;
}
serializedEntries
.FindPropertyRelative("m_Entries")
.GetArrayElementAtIndex(index)
.FindPropertyRelative("m_NodeName")
.stringValue = subgraph.assetGuid;
ApplyChanges();
}
}
void ConfigureCategoryColumn(Column c, SerializedProperty serializedEntries, HeatmapEntries entries)
{
c.makeCell = () =>
{
var ve = new IntegerField
{
tooltip = "Category assigned to this node, which determines its color.",
isDelayed = true,
};
ve.AddToClassList(k_HeatFieldUssClassName);
return ve;
};
c.bindCell = (v, i) =>
{
var intField = (IntegerField) v;
intField.SetValueWithoutNotify(entries.Entries[i].m_Category);
intField.RegisterCallback<ChangeEvent<int>, int>(OnHeatChanged, i);
};
c.unbindCell = (v, i) =>
{
var intField = (IntegerField) v;
intField.UnregisterCallback<ChangeEvent<int>, int>(OnHeatChanged);
};
return;
void OnHeatChanged(ChangeEvent<int> changeEvent, int index)
{
serializedEntries
.FindPropertyRelative("m_Entries")
.GetArrayElementAtIndex(index)
.FindPropertyRelative("m_Category")
.intValue = changeEvent.newValue;
ApplyChanges();
}
}
void OnEnable()
{
Undo.undoRedoPerformed += OnUndoRedo;
}
void OnDisable()
{
Undo.undoRedoPerformed -= OnUndoRedo;
}
void OnUndoRedo()
{
// Target might not be what we expect if our inspector isn't focused.
if (HeatmapValuesTarget != null)
{
m_RefreshNodesHint.EnableInClassList(k_HelpBoxHiddenUssModifier, HeatmapValuesTarget.ContainsAllApplicableNodes());
}
UpdateShaderGraphWindows();
}
void RefreshNodeListFromProject()
{
Undo.RecordObject(target, $"Refresh Node List in {target.name}");
HeatmapValuesTarget.PopulateNodesFromProject();
serializedObject.Update();
m_NodesListView.RefreshItems();
m_RefreshNodesHint.EnableInClassList(k_HelpBoxHiddenUssModifier, true);
UpdateShaderGraphWindows();
}
void ApplyChanges()
{
serializedObject.ApplyModifiedProperties();
UpdateShaderGraphWindows();
}
public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();
AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(k_TemplatePath).CloneTree(root);
var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(k_StylePath);
root.styleSheets.Add(styleSheet);
var colorsListView = root.Q<ListView>(k_ColorsListName);
colorsListView.bindingPath = serializedObject.FindProperty(nameof(ShaderGraphHeatmapValues.m_Colors)).propertyPath;
colorsListView.makeItem = () => new ColorField {showAlpha = false, hdr = false};
colorsListView.bindItem = (v, i) =>
{
var colorField = (ColorField) v;
colorField.label = $"Category {i}";
colorField.tooltip = $"Color for category {i}.";
colorField.RegisterValueChangedCallback(_ => ApplyChanges());
// Assigning bindingPath makes itemsSource into a list of SerializedProperties, so we can bind directly.
colorField.BindProperty((SerializedProperty) colorsListView.itemsSource[i]);
};
colorsListView.Bind(serializedObject);
var nodeEntries = HeatmapValuesTarget.Nodes;
m_NodesListView = root.Q<MultiColumnListView>(k_NodeListName);
m_RefreshNodesHint = root.Q<HelpBox>(k_RefreshNodesHintName);
m_RefreshNodesHint.EnableInClassList(k_HelpBoxHiddenUssModifier, HeatmapValuesTarget.ContainsAllApplicableNodes());
m_RefreshNodesHint.Q<Button>(k_RefreshNodesButtonName).clicked += RefreshNodeListFromProject;
var nodesSerializedProperty = serializedObject.FindProperty("m_Nodes");
ConfigureNodeLabelColumn(m_NodesListView.columns[k_NodeTitleColumnName], nodeEntries);
ConfigureCategoryColumn(m_NodesListView.columns[k_HeatValueColumnName], nodesSerializedProperty, nodeEntries);
m_NodesListView.AddManipulator(new ContextualMenuManipulator(evt =>
{
evt.menu.AppendSeparator();
evt.menu.AppendAction("Refresh Node List", _ => RefreshNodeListFromProject());
}));
// MultiColumnListView doesn't support binding as of writing, but we can still track property changes
// to reflect undo, redo, reset, etc.
m_NodesListView.TrackPropertyValue(nodesSerializedProperty, _ => { m_NodesListView.RefreshItems(); });
m_NodesListView.itemsSource = nodeEntries.Entries;
var subgraphEntries = HeatmapValuesTarget.Subgraphs;
m_SubgraphListView = root.Q<MultiColumnListView>(k_SubgraphListName);
var subgraphsSerializedProperty = serializedObject.FindProperty("m_Subgraphs");
ConfigureSubgraphPickerColumn(m_SubgraphListView.columns[k_SubgraphColumnName]);
ConfigureCategoryColumn(m_SubgraphListView.columns[k_HeatValueColumnName], subgraphsSerializedProperty, subgraphEntries);
m_SubgraphListView.TrackPropertyValue(subgraphsSerializedProperty, _ => { m_SubgraphListView.RefreshItems(); });
m_SubgraphListView.itemsSource = subgraphEntries.Entries;
return root;
}
}
}