A game about forced loneliness, made by TACStudios
1using System;
2using System.Linq;
3using UnityEditor.Experimental.GraphView;
4using UnityEditor.Graphing;
5using UnityEditor.Rendering;
6using UnityEditor.ShaderGraph.Drawing;
7using UnityEditor.ShaderGraph.Drawing.Controls;
8using UnityEditor.ShaderGraph.Internal;
9using UnityEngine;
10using UnityEngine.UIElements;
11using UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers;
12using ContextualMenuManipulator = UnityEngine.UIElements.ContextualMenuManipulator;
13
14namespace UnityEditor.ShaderGraph
15{
16 sealed class PropertyNodeView : TokenNode, IShaderNodeView, IInspectable, IShaderInputObserver
17 {
18 static readonly Texture2D exposedIcon = Resources.Load<Texture2D>("GraphView/Nodes/BlackboardFieldExposed");
19 public static StyleSheet styleSheet;
20
21 // When the properties are changed, this delegate is used to trigger an update in the view that represents those properties
22 Action m_TriggerInspectorUpdate;
23
24 Action m_ResetReferenceNameAction;
25
26 public PropertyNodeView(PropertyNode node, EdgeConnectorListener edgeConnectorListener)
27 : base(null, ShaderPort.Create(node.GetOutputSlots<MaterialSlot>().First(), edgeConnectorListener))
28 {
29 if (styleSheet == null)
30 styleSheet = Resources.Load<StyleSheet>("Styles/PropertyNodeView");
31 styleSheets.Add(styleSheet);
32
33 this.node = node;
34 viewDataKey = node.objectId.ToString();
35 userData = node;
36
37 // Getting the generatePropertyBlock property to see if it is exposed or not
38 UpdateIcon();
39
40 // Setting the position of the node, otherwise it ends up in the center of the canvas
41 SetPosition(new Rect(node.drawState.position.x, node.drawState.position.y, 0, 0));
42
43 // Removing the title label since it is not used and taking up space
44 this.Q("title-label").RemoveFromHierarchy();
45
46 // Add disabled overlay
47 Add(new VisualElement() { name = "disabledOverlay", pickingMode = PickingMode.Ignore });
48
49 // Update active state
50 SetActive(node.isActive);
51
52 // Registering the hovering callbacks for highlighting
53 RegisterCallback<MouseEnterEvent>(OnMouseHover);
54 RegisterCallback<MouseLeaveEvent>(OnMouseHover);
55
56 // add the right click context menu
57 IManipulator contextMenuManipulator = new ContextualMenuManipulator(AddContextMenuOptions);
58 this.AddManipulator(contextMenuManipulator);
59
60 if (property != null)
61 {
62 property.onAfterVersionChange += () =>
63 {
64 m_TriggerInspectorUpdate?.Invoke();
65 };
66 }
67 }
68
69 // Updating the text label of the output slot
70 void UpdateDisplayName()
71 {
72 var slot = node.GetSlots<MaterialSlot>().ToList().First();
73 this.Q<Label>("type").text = slot.displayName;
74 }
75
76 public Node gvNode => this;
77 public AbstractMaterialNode node { get; }
78 public VisualElement colorElement => null;
79 public string inspectorTitle => $"{property.displayName} (Node)";
80
81 [Inspectable("ShaderInput", null)]
82 AbstractShaderProperty property => (node as PropertyNode)?.property;
83
84 public object GetObjectToInspect()
85 {
86 return property;
87 }
88
89 public void SupplyDataToPropertyDrawer(IPropertyDrawer propertyDrawer, Action inspectorUpdateDelegate)
90 {
91 if (propertyDrawer is ShaderInputPropertyDrawer shaderInputPropertyDrawer)
92 {
93 var propNode = node as PropertyNode;
94 var graph = node.owner as GraphData;
95
96 var shaderInputViewModel = new ShaderInputViewModel()
97 {
98 model = property,
99 parentView = null,
100 isSubGraph = graph.isSubGraph,
101 isInputExposed = property.isExposed,
102 inputName = property.displayName,
103 inputTypeName = property.GetPropertyTypeString(),
104 requestModelChangeAction = this.RequestModelChange
105 };
106 shaderInputPropertyDrawer.GetViewModel(shaderInputViewModel, node.owner, this.OnDisplayNameUpdated);
107
108 this.m_TriggerInspectorUpdate = inspectorUpdateDelegate;
109 this.m_ResetReferenceNameAction = shaderInputPropertyDrawer.ResetReferenceName;
110 }
111 }
112
113 void RequestModelChange(IGraphDataAction changeAction)
114 {
115 node.owner?.owner.graphDataStore.Dispatch(changeAction);
116 }
117
118 internal static void AddMainColorMenuOptions(ContextualMenuPopulateEvent evt, ColorShaderProperty colorProp, GraphData graphData, Action inspectorUpdateAction)
119 {
120 if (!graphData.isSubGraph)
121 {
122 if (!colorProp.isMainColor)
123 {
124 evt.menu.AppendAction(
125 "Set as Main Color",
126 e =>
127 {
128 ColorShaderProperty col = graphData.GetMainColor();
129 if (col != null)
130 {
131 if (EditorUtility.DisplayDialog("Change Main Color Action", $"Are you sure you want to change the Main Color from {col.displayName} to {colorProp.displayName}?", "Yes", "Cancel"))
132 {
133 graphData.owner.RegisterCompleteObjectUndo("Change Main Color");
134 col.isMainColor = false;
135 colorProp.isMainColor = true;
136 inspectorUpdateAction();
137 }
138 return;
139 }
140
141 graphData.owner.RegisterCompleteObjectUndo("Set Main Color");
142 colorProp.isMainColor = true;
143 inspectorUpdateAction();
144 });
145 }
146 else
147 {
148 evt.menu.AppendAction(
149 "Clear Main Color",
150 e =>
151 {
152 graphData.owner.RegisterCompleteObjectUndo("Clear Main Color");
153 colorProp.isMainColor = false;
154 inspectorUpdateAction();
155 });
156 }
157 }
158 }
159
160 internal static void AddMainTextureMenuOptions(ContextualMenuPopulateEvent evt, Texture2DShaderProperty texProp, GraphData graphData, Action inspectorUpdateAction)
161 {
162 if (!graphData.isSubGraph)
163 {
164 if (!texProp.isMainTexture)
165 {
166 evt.menu.AppendAction(
167 "Set as Main Texture",
168 e =>
169 {
170 Texture2DShaderProperty tex = graphData.GetMainTexture();
171 // There's already a main texture, ask the user if they want to change and toggle the old one to not be main
172 if (tex != null)
173 {
174 if (EditorUtility.DisplayDialog("Change Main Texture Action", $"Are you sure you want to change the Main Texture from {tex.displayName} to {texProp.displayName}?", "Yes", "Cancel"))
175 {
176 graphData.owner.RegisterCompleteObjectUndo("Change Main Texture");
177 tex.isMainTexture = false;
178 texProp.isMainTexture = true;
179 inspectorUpdateAction();
180 }
181 return;
182 }
183
184 graphData.owner.RegisterCompleteObjectUndo("Set Main Texture");
185 texProp.isMainTexture = true;
186 inspectorUpdateAction();
187 });
188 }
189 else
190 {
191 evt.menu.AppendAction(
192 "Clear Main Texture",
193 e =>
194 {
195 graphData.owner.RegisterCompleteObjectUndo("Clear Main Texture");
196 texProp.isMainTexture = false;
197 inspectorUpdateAction();
198 });
199 }
200 }
201 }
202
203 void AddContextMenuOptions(ContextualMenuPopulateEvent evt)
204 {
205 // Checks if the reference name has been overridden and appends menu action to reset it, if so
206 if (property.isRenamable &&
207 !string.IsNullOrEmpty(property.overrideReferenceName))
208 {
209 evt.menu.AppendAction(
210 "Reset Reference",
211 e =>
212 {
213 m_ResetReferenceNameAction();
214 DirtyNodes(ModificationScope.Graph);
215 },
216 DropdownMenuAction.AlwaysEnabled);
217 }
218
219 if (property is ColorShaderProperty colorProp)
220 {
221 AddMainColorMenuOptions(evt, colorProp, node.owner, m_TriggerInspectorUpdate);
222 }
223
224 if (property is Texture2DShaderProperty texProp)
225 {
226 AddMainTextureMenuOptions(evt, texProp, node.owner, m_TriggerInspectorUpdate);
227 }
228 }
229
230 void OnDisplayNameUpdated(bool triggerPropertyViewUpdate = false, ModificationScope modificationScope = ModificationScope.Node)
231 {
232 if (triggerPropertyViewUpdate)
233 m_TriggerInspectorUpdate?.Invoke();
234
235 UpdateDisplayName();
236 }
237
238 void DirtyNodes(ModificationScope modificationScope = ModificationScope.Node)
239 {
240 var graph = node.owner as GraphData;
241
242 var colorManager = GetFirstAncestorOfType<GraphEditorView>().colorManager;
243 var nodes = GetFirstAncestorOfType<GraphEditorView>().graphView.Query<MaterialNodeView>().ToList();
244
245 colorManager.SetNodesDirty(nodes);
246 colorManager.UpdateNodeViews(nodes);
247
248 foreach (var node in graph.GetNodes<PropertyNode>())
249 {
250 node.Dirty(modificationScope);
251 }
252 }
253
254 public void SetColor(Color newColor)
255 {
256 // Nothing to do here yet
257 }
258
259 public void ResetColor()
260 {
261 // Nothing to do here yet
262 }
263
264 public void UpdatePortInputTypes()
265 {
266 }
267
268 public void UpdateDropdownEntries()
269 {
270 }
271
272 public bool FindPort(SlotReference slot, out ShaderPort port)
273 {
274 port = output as ShaderPort;
275 return port != null && port.slot.slotReference.Equals(slot);
276 }
277
278 void UpdateIcon()
279 {
280 var graph = node?.owner as GraphData;
281 if ((graph != null) && (property != null))
282 icon = (graph.isSubGraph || property.isExposed) ? exposedIcon : null;
283 else
284 icon = null;
285 }
286
287 public void OnModified(ModificationScope scope)
288 {
289 //disconnected property nodes are always active
290 if (!node.IsSlotConnected(PropertyNode.OutputSlotId) && node.activeState is AbstractMaterialNode.ActiveState.Implicit)
291 node.SetActive(true);
292
293 SetActive(node.isActive);
294
295 if (scope == ModificationScope.Graph)
296 {
297 UpdateIcon();
298 }
299
300 if (scope == ModificationScope.Topological || scope == ModificationScope.Node)
301 {
302 UpdateDisplayName();
303 }
304 }
305
306 public void SetActive(bool state)
307 {
308 // Setup
309 var disabledString = "disabled";
310
311 if (!state)
312 {
313 // Add elements to disabled class list
314 AddToClassList(disabledString);
315 }
316 else
317 {
318 // Remove elements from disabled class list
319 RemoveFromClassList(disabledString);
320 }
321 }
322
323 public void AttachMessage(string errString, ShaderCompilerMessageSeverity severity)
324 {
325 ClearMessage();
326 IconBadge badge;
327 if (severity == ShaderCompilerMessageSeverity.Error)
328 {
329 badge = IconBadge.CreateError(errString);
330 }
331 else
332 {
333 badge = IconBadge.CreateComment(errString);
334 }
335
336 Add(badge);
337 badge.AttachTo(this, SpriteAlignment.RightCenter);
338 }
339
340 public void ClearMessage()
341 {
342 var badge = this.Q<IconBadge>();
343 if (badge != null)
344 {
345 badge.Detach();
346 badge.RemoveFromHierarchy();
347 }
348 }
349
350 SGBlackboardRow GetAssociatedBlackboardRow()
351 {
352 var graphView = GetFirstAncestorOfType<GraphEditorView>();
353
354 var blackboardController = graphView?.blackboardController;
355 if (blackboardController == null)
356 return null;
357
358 var propNode = (PropertyNode)node;
359 return blackboardController.GetBlackboardRow(propNode.property);
360 }
361
362 void OnMouseHover(EventBase evt)
363 {
364 var propRow = GetAssociatedBlackboardRow();
365 if (propRow != null)
366 {
367 if (evt.eventTypeId == MouseEnterEvent.TypeId())
368 {
369 propRow.AddToClassList("hovered");
370 }
371 else
372 {
373 propRow.RemoveFromClassList("hovered");
374 }
375 }
376 }
377
378 public void Dispose()
379 {
380 var propRow = GetAssociatedBlackboardRow();
381 // If this node view is deleted, remove highlighting from associated blackboard row
382 if (propRow != null)
383 {
384 propRow.RemoveFromClassList("hovered");
385 }
386 styleSheets.Clear();
387 m_TriggerInspectorUpdate = null;
388 m_ResetReferenceNameAction = null;
389 UnregisterCallback<MouseEnterEvent>(OnMouseHover);
390 UnregisterCallback<MouseLeaveEvent>(OnMouseHover);
391 }
392
393 public void OnShaderInputUpdated(ModificationScope modificationScope)
394 {
395 if (modificationScope == ModificationScope.Layout)
396 UpdateDisplayName();
397 }
398 }
399}