A game about forced loneliness, made by TACStudios
at master 12 kB view raw
1using System; 2using System.Collections.Generic; 3using UnityEditor.ShaderGraph.Drawing; 4using UnityEditor.ShaderGraph.Drawing.Colors; 5using UnityEditor.UIElements; 6using UnityEngine; 7using UnityEngine.UIElements; 8 9namespace UnityEditor.ShaderGraph 10{ 11 [CustomEditor(typeof(ShaderGraphHeatmapValues))] 12 class ShaderGraphHeatmapValuesEditor : Editor 13 { 14 const string k_TemplatePath = "Packages/com.unity.shadergraph/Editor/Resources/UXML/HeatmapValuesEditor.uxml"; 15 const string k_StylePath = "Packages/com.unity.shadergraph/Editor/Resources/Styles/HeatmapValuesEditor.uss"; 16 17 const string k_ColorsListName = "colors-list"; 18 const string k_NodeListName = "nodes-list"; 19 const string k_SubgraphListName = "subgraph-list"; 20 const string k_NodeTitleColumnName = "node"; 21 const string k_SubgraphColumnName = "subgraph"; 22 const string k_HeatValueColumnName = "value"; 23 24 const string k_HeatFieldUssClassName = "sg-heatmap__heat-field"; 25 const string k_SubgraphPickerUssClassName = "sg-heatmap__subgraph-picker"; 26 const string k_NodeLabelUssClassName = "sg-heatmap__node-label"; 27 28 const string k_RefreshNodesHintName = "refresh-nodes-hint"; 29 const string k_RefreshNodesButtonName = "refresh-nodes-button"; 30 const string k_HelpBoxHiddenUssModifier = "sg-heatmap__help-box--hidden"; 31 32 ShaderGraphHeatmapValues HeatmapValuesTarget => target as ShaderGraphHeatmapValues; 33 34 MultiColumnListView m_NodesListView; 35 MultiColumnListView m_SubgraphListView; 36 VisualElement m_RefreshNodesHint; 37 38 internal static void UpdateShaderGraphWindows() 39 { 40 foreach (var window in Resources.FindObjectsOfTypeAll<MaterialGraphEditWindow>()) 41 { 42 var graphEditorView = window.graphEditorView; 43 if (graphEditorView == null) 44 { 45 continue; 46 } 47 48 var colorManager = graphEditorView.colorManager; 49 if (colorManager.activeProviderName != HeatmapColors.Title) 50 { 51 continue; 52 } 53 54 var nodeList = graphEditorView.Query<MaterialNodeView>().ToList(); 55 colorManager.UpdateNodeViews(nodeList); 56 } 57 } 58 59 static Dictionary<string, string> s_NodeTypeNamesToTitles; 60 static Dictionary<string, string> NodeTypeNamesToTitles 61 { 62 get 63 { 64 if (s_NodeTypeNamesToTitles is null) 65 { 66 s_NodeTypeNamesToTitles = new Dictionary<string, string>(); 67 foreach (var knownNodeType in NodeClassCache.knownNodeTypes) 68 { 69 var node = (AbstractMaterialNode) Activator.CreateInstance(knownNodeType); 70 s_NodeTypeNamesToTitles[knownNodeType.Name] = node.name; 71 } 72 } 73 74 return s_NodeTypeNamesToTitles; 75 } 76 } 77 78 static string GetTitleForNode(string nodeTypeName) 79 { 80 return string.IsNullOrEmpty(nodeTypeName) 81 ? string.Empty 82 : NodeTypeNamesToTitles.GetValueOrDefault(nodeTypeName, nodeTypeName); 83 } 84 85 static void ConfigureNodeLabelColumn(Column column, HeatmapEntries entries) 86 { 87 column.makeCell = () => 88 { 89 var ve = new Label(); 90 ve.AddToClassList(k_NodeLabelUssClassName); 91 return ve; 92 }; 93 column.bindCell = (v, i) => 94 { 95 var label = (Label) v; 96 label.text = GetTitleForNode(entries.Entries[i].m_NodeName); 97 }; 98 } 99 100 void ConfigureSubgraphPickerColumn(Column column) 101 { 102 var serializedEntries = serializedObject.FindProperty("m_Subgraphs"); 103 var entries = HeatmapValuesTarget.Subgraphs; 104 105 column.makeCell = () => 106 { 107 var ve = new ObjectField 108 { 109 tooltip = "Subgraph asset.", 110 objectType = typeof(SubGraphAsset), 111 allowSceneObjects = false, 112 }; 113 114 ve.AddToClassList(k_SubgraphPickerUssClassName); 115 return ve; 116 }; 117 118 column.bindCell = (v, i) => 119 { 120 var objectField = (ObjectField) v; 121 objectField.RegisterCallback<ChangeEvent<UnityEngine.Object>, int>(OnSubgraphChanged, i); 122 123 if (string.IsNullOrEmpty(entries.Entries[i].m_NodeName)) 124 { 125 return; 126 } 127 128 var assetPath = AssetDatabase.GUIDToAssetPath(entries.Entries[i].m_NodeName); 129 var asset = AssetDatabase.LoadAssetAtPath<SubGraphAsset>(assetPath); 130 objectField.SetValueWithoutNotify(asset); 131 }; 132 133 column.unbindCell = (v, i) => 134 { 135 var objectField = (ObjectField) v; 136 objectField.UnregisterCallback<ChangeEvent<UnityEngine.Object>, int>(OnSubgraphChanged); 137 }; 138 139 return; 140 141 void OnSubgraphChanged(ChangeEvent<UnityEngine.Object> changeEvent, int index) 142 { 143 if (changeEvent.newValue is not SubGraphAsset subgraph) 144 { 145 return; 146 } 147 148 serializedEntries 149 .FindPropertyRelative("m_Entries") 150 .GetArrayElementAtIndex(index) 151 .FindPropertyRelative("m_NodeName") 152 .stringValue = subgraph.assetGuid; 153 154 ApplyChanges(); 155 } 156 } 157 158 void ConfigureCategoryColumn(Column c, SerializedProperty serializedEntries, HeatmapEntries entries) 159 { 160 c.makeCell = () => 161 { 162 var ve = new IntegerField 163 { 164 tooltip = "Category assigned to this node, which determines its color.", 165 isDelayed = true, 166 }; 167 168 ve.AddToClassList(k_HeatFieldUssClassName); 169 return ve; 170 }; 171 c.bindCell = (v, i) => 172 { 173 var intField = (IntegerField) v; 174 intField.SetValueWithoutNotify(entries.Entries[i].m_Category); 175 intField.RegisterCallback<ChangeEvent<int>, int>(OnHeatChanged, i); 176 }; 177 c.unbindCell = (v, i) => 178 { 179 var intField = (IntegerField) v; 180 intField.UnregisterCallback<ChangeEvent<int>, int>(OnHeatChanged); 181 }; 182 183 return; 184 185 void OnHeatChanged(ChangeEvent<int> changeEvent, int index) 186 { 187 serializedEntries 188 .FindPropertyRelative("m_Entries") 189 .GetArrayElementAtIndex(index) 190 .FindPropertyRelative("m_Category") 191 .intValue = changeEvent.newValue; 192 193 ApplyChanges(); 194 } 195 } 196 197 void OnEnable() 198 { 199 Undo.undoRedoPerformed += OnUndoRedo; 200 } 201 202 void OnDisable() 203 { 204 Undo.undoRedoPerformed -= OnUndoRedo; 205 } 206 207 void OnUndoRedo() 208 { 209 // Target might not be what we expect if our inspector isn't focused. 210 if (HeatmapValuesTarget != null) 211 { 212 m_RefreshNodesHint.EnableInClassList(k_HelpBoxHiddenUssModifier, HeatmapValuesTarget.ContainsAllApplicableNodes()); 213 } 214 215 UpdateShaderGraphWindows(); 216 } 217 218 void RefreshNodeListFromProject() 219 { 220 Undo.RecordObject(target, $"Refresh Node List in {target.name}"); 221 222 HeatmapValuesTarget.PopulateNodesFromProject(); 223 serializedObject.Update(); 224 225 m_NodesListView.RefreshItems(); 226 m_RefreshNodesHint.EnableInClassList(k_HelpBoxHiddenUssModifier, true); 227 228 UpdateShaderGraphWindows(); 229 } 230 231 void ApplyChanges() 232 { 233 serializedObject.ApplyModifiedProperties(); 234 UpdateShaderGraphWindows(); 235 } 236 237 public override VisualElement CreateInspectorGUI() 238 { 239 var root = new VisualElement(); 240 AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(k_TemplatePath).CloneTree(root); 241 242 var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(k_StylePath); 243 root.styleSheets.Add(styleSheet); 244 245 var colorsListView = root.Q<ListView>(k_ColorsListName); 246 colorsListView.bindingPath = serializedObject.FindProperty(nameof(ShaderGraphHeatmapValues.m_Colors)).propertyPath; 247 colorsListView.makeItem = () => new ColorField {showAlpha = false, hdr = false}; 248 colorsListView.bindItem = (v, i) => 249 { 250 var colorField = (ColorField) v; 251 colorField.label = $"Category {i}"; 252 colorField.tooltip = $"Color for category {i}."; 253 colorField.RegisterValueChangedCallback(_ => ApplyChanges()); 254 255 // Assigning bindingPath makes itemsSource into a list of SerializedProperties, so we can bind directly. 256 colorField.BindProperty((SerializedProperty) colorsListView.itemsSource[i]); 257 }; 258 259 colorsListView.Bind(serializedObject); 260 261 var nodeEntries = HeatmapValuesTarget.Nodes; 262 m_NodesListView = root.Q<MultiColumnListView>(k_NodeListName); 263 264 m_RefreshNodesHint = root.Q<HelpBox>(k_RefreshNodesHintName); 265 m_RefreshNodesHint.EnableInClassList(k_HelpBoxHiddenUssModifier, HeatmapValuesTarget.ContainsAllApplicableNodes()); 266 m_RefreshNodesHint.Q<Button>(k_RefreshNodesButtonName).clicked += RefreshNodeListFromProject; 267 268 var nodesSerializedProperty = serializedObject.FindProperty("m_Nodes"); 269 ConfigureNodeLabelColumn(m_NodesListView.columns[k_NodeTitleColumnName], nodeEntries); 270 ConfigureCategoryColumn(m_NodesListView.columns[k_HeatValueColumnName], nodesSerializedProperty, nodeEntries); 271 272 m_NodesListView.AddManipulator(new ContextualMenuManipulator(evt => 273 { 274 evt.menu.AppendSeparator(); 275 evt.menu.AppendAction("Refresh Node List", _ => RefreshNodeListFromProject()); 276 })); 277 278 // MultiColumnListView doesn't support binding as of writing, but we can still track property changes 279 // to reflect undo, redo, reset, etc. 280 m_NodesListView.TrackPropertyValue(nodesSerializedProperty, _ => { m_NodesListView.RefreshItems(); }); 281 m_NodesListView.itemsSource = nodeEntries.Entries; 282 283 var subgraphEntries = HeatmapValuesTarget.Subgraphs; 284 m_SubgraphListView = root.Q<MultiColumnListView>(k_SubgraphListName); 285 286 var subgraphsSerializedProperty = serializedObject.FindProperty("m_Subgraphs"); 287 ConfigureSubgraphPickerColumn(m_SubgraphListView.columns[k_SubgraphColumnName]); 288 ConfigureCategoryColumn(m_SubgraphListView.columns[k_HeatValueColumnName], subgraphsSerializedProperty, subgraphEntries); 289 290 m_SubgraphListView.TrackPropertyValue(subgraphsSerializedProperty, _ => { m_SubgraphListView.RefreshItems(); }); 291 m_SubgraphListView.itemsSource = subgraphEntries.Entries; 292 293 return root; 294 } 295 } 296}