A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Reflection;
5using UnityEngine;
6using UnityEditor.Graphing;
7using UnityEditor.Graphing.Util;
8using UnityEditor.ShaderGraph.Drawing.Controls;
9using UnityEngine.Rendering;
10using UnityEditor.Experimental.GraphView;
11using UnityEditor.Rendering;
12using UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers;
13using UnityEditor.ShaderGraph.Internal;
14using UnityEngine.UIElements;
15using UnityEditor.UIElements;
16using Node = UnityEditor.Experimental.GraphView.Node;
17
18namespace UnityEditor.ShaderGraph.Drawing
19{
20 sealed class MaterialNodeView : Node, IShaderNodeView, IInspectable
21 {
22 PreviewRenderData m_PreviewRenderData;
23 Image m_PreviewImage;
24 // Remove this after updated to the correct API call has landed in trunk. ------------
25 VisualElement m_TitleContainer;
26
27 VisualElement m_PreviewContainer;
28 VisualElement m_PreviewFiller;
29 VisualElement m_ControlItems;
30 VisualElement m_ControlsDivider;
31 VisualElement m_DropdownItems;
32 VisualElement m_DropdownsDivider;
33 Action m_UnregisterAll;
34
35 IEdgeConnectorListener m_ConnectorListener;
36
37 MaterialGraphView m_GraphView;
38
39 public string inspectorTitle => $"{node.name} (Node)";
40 public void Initialize(AbstractMaterialNode inNode, PreviewManager previewManager, IEdgeConnectorListener connectorListener, MaterialGraphView graphView)
41 {
42 styleSheets.Add(Resources.Load<StyleSheet>("Styles/MaterialNodeView"));
43 styleSheets.Add(Resources.Load<StyleSheet>($"Styles/ColorMode"));
44 AddToClassList("MaterialNode");
45
46 if (inNode == null)
47 return;
48
49 var contents = this.Q("contents");
50
51 m_GraphView = graphView;
52 mainContainer.style.overflow = StyleKeyword.None; // Override explicit style set in base class
53 m_ConnectorListener = connectorListener;
54 node = inNode;
55 viewDataKey = node.objectId;
56 UpdateTitle();
57
58 // Add disabled overlay
59 Add(new VisualElement() { name = "disabledOverlay", pickingMode = PickingMode.Ignore });
60
61 // Add controls container
62 var controlsContainer = new VisualElement { name = "controls" };
63 {
64 m_ControlsDivider = new VisualElement { name = "divider" };
65 m_ControlsDivider.AddToClassList("horizontal");
66 controlsContainer.Add(m_ControlsDivider);
67 m_ControlItems = new VisualElement { name = "items" };
68 controlsContainer.Add(m_ControlItems);
69
70 // Instantiate control views from node
71 foreach (var propertyInfo in node.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
72 foreach (IControlAttribute attribute in propertyInfo.GetCustomAttributes(typeof(IControlAttribute), false))
73 m_ControlItems.Add(attribute.InstantiateControl(node, propertyInfo));
74 }
75 if (m_ControlItems.childCount > 0)
76 contents.Add(controlsContainer);
77
78 // Add dropdowns container
79 if (inNode is SubGraphNode)
80 {
81 var dropdownContainer = new VisualElement { name = "dropdowns" };
82 {
83 m_DropdownsDivider = new VisualElement { name = "divider" };
84 m_DropdownsDivider.AddToClassList("horizontal");
85 dropdownContainer.Add(m_DropdownsDivider);
86 m_DropdownItems = new VisualElement { name = "items" };
87 dropdownContainer.Add(m_DropdownItems);
88 UpdateDropdownEntries();
89 }
90 contents.Add(dropdownContainer);
91 }
92
93 if (node.hasPreview)
94 {
95 // Add actual preview which floats on top of the node
96 m_PreviewContainer = new VisualElement
97 {
98 name = "previewContainer",
99 style = { overflow = Overflow.Hidden },
100 pickingMode = PickingMode.Ignore
101 };
102 m_PreviewImage = new Image
103 {
104 name = "preview",
105 pickingMode = PickingMode.Ignore,
106 image = Texture2D.whiteTexture,
107 };
108 {
109 // Add preview collapse button on top of preview
110 var collapsePreviewButton = new VisualElement { name = "collapse" };
111 collapsePreviewButton.Add(new VisualElement { name = "icon" });
112 collapsePreviewButton.AddManipulator(new Clickable(() =>
113 {
114 SetPreviewExpandedStateOnSelection(false);
115 }));
116 m_PreviewImage.Add(collapsePreviewButton);
117 }
118 m_PreviewContainer.Add(m_PreviewImage);
119
120 // Hook up preview image to preview manager
121 m_PreviewRenderData = previewManager.GetPreviewRenderData(inNode);
122 m_PreviewRenderData.onPreviewChanged += UpdatePreviewTexture;
123 UpdatePreviewTexture();
124
125 // Add fake preview which pads out the node to provide space for the floating preview
126 m_PreviewFiller = new VisualElement { name = "previewFiller" };
127 m_PreviewFiller.AddToClassList("expanded");
128 {
129 var previewDivider = new VisualElement { name = "divider" };
130 previewDivider.AddToClassList("horizontal");
131 m_PreviewFiller.Add(previewDivider);
132
133 var expandPreviewButton = new VisualElement { name = "expand" };
134 expandPreviewButton.Add(new VisualElement { name = "icon" });
135 expandPreviewButton.AddManipulator(new Clickable(() =>
136 {
137 SetPreviewExpandedStateOnSelection(true);
138 }));
139 m_PreviewFiller.Add(expandPreviewButton);
140 }
141 contents.Add(m_PreviewFiller);
142
143 UpdatePreviewExpandedState(node.previewExpanded);
144 }
145
146 base.expanded = node.drawState.expanded;
147 AddSlots(node.GetSlots<MaterialSlot>());
148
149 switch (node)
150 {
151 case SubGraphNode:
152 RegisterCallback<MouseDownEvent>(OnSubGraphDoubleClick);
153 m_UnregisterAll += () => { UnregisterCallback<MouseDownEvent>(OnSubGraphDoubleClick); };
154 break;
155 }
156
157 m_TitleContainer = this.Q("title");
158
159 if (node is BlockNode blockData)
160 {
161 AddToClassList("blockData");
162 m_TitleContainer.RemoveFromHierarchy();
163 }
164 else
165 {
166 SetPosition(new Rect(node.drawState.position.x, node.drawState.position.y, 0, 0));
167 }
168
169 // Update active state
170 SetActive(node.isActive);
171
172 // Register OnMouseHover callbacks for node highlighting
173 RegisterCallback<MouseEnterEvent>(OnMouseHover);
174 m_UnregisterAll += () => { UnregisterCallback<MouseEnterEvent>(OnMouseHover); };
175
176 RegisterCallback<MouseLeaveEvent>(OnMouseHover);
177 m_UnregisterAll += () => { UnregisterCallback<MouseLeaveEvent>(OnMouseHover); };
178
179 ShaderGraphPreferences.onAllowDeprecatedChanged += UpdateTitle;
180 }
181
182 public bool FindPort(SlotReference slotRef, out ShaderPort port)
183 {
184 port = inputContainer.Query<ShaderPort>().ToList()
185 .Concat(outputContainer.Query<ShaderPort>().ToList())
186 .First(p => p.slot.slotReference.Equals(slotRef));
187
188 return port != null;
189 }
190
191 public void AttachMessage(string errString, ShaderCompilerMessageSeverity severity)
192 {
193 ClearMessage();
194 IconBadge badge;
195 if (severity == ShaderCompilerMessageSeverity.Error)
196 {
197 badge = IconBadge.CreateError(errString);
198 }
199 else
200 {
201 badge = IconBadge.CreateComment(errString);
202 }
203
204 Add(badge);
205
206 if (node is BlockNode)
207 {
208 FindPort(node.GetSlotReference(0), out var port);
209 badge.AttachTo(port.parent, SpriteAlignment.RightCenter);
210 }
211 else
212 {
213 badge.AttachTo(m_TitleContainer, SpriteAlignment.RightCenter);
214 }
215 }
216
217 public void SetActive(bool state)
218 {
219 // Setup
220 var disabledString = "disabled";
221 var portDisabledString = "inactive";
222
223
224 if (!state)
225 {
226 // Add elements to disabled class list
227 AddToClassList(disabledString);
228
229 var inputPorts = inputContainer.Query<ShaderPort>().ToList();
230 foreach (var port in inputPorts)
231 {
232 port.AddToClassList(portDisabledString);
233 }
234 var outputPorts = outputContainer.Query<ShaderPort>().ToList();
235 foreach (var port in outputPorts)
236 {
237 port.AddToClassList(portDisabledString);
238 }
239 }
240 else
241 {
242 // Remove elements from disabled class list
243 RemoveFromClassList(disabledString);
244
245 var inputPorts = inputContainer.Query<ShaderPort>().ToList();
246 foreach (var port in inputPorts)
247 {
248 port.RemoveFromClassList(portDisabledString);
249 }
250 var outputPorts = outputContainer.Query<ShaderPort>().ToList();
251 foreach (var port in outputPorts)
252 {
253 port.RemoveFromClassList(portDisabledString);
254 }
255 }
256 }
257
258 public void ClearMessage()
259 {
260 var badge = this.Q<IconBadge>();
261 badge?.Detach();
262 badge?.RemoveFromHierarchy();
263 }
264
265 public void UpdateDropdownEntries()
266 {
267 if (node is SubGraphNode subGraphNode && subGraphNode.asset != null)
268 {
269 m_DropdownItems.Clear();
270 var dropdowns = subGraphNode.asset.dropdowns;
271 foreach (var dropdown in dropdowns)
272 {
273 if (dropdown.isExposed)
274 {
275 var name = subGraphNode.GetDropdownEntryName(dropdown.referenceName);
276 if (!dropdown.ContainsEntry(name))
277 {
278 name = dropdown.entryName;
279 subGraphNode.SetDropdownEntryName(dropdown.referenceName, name);
280 }
281
282 var field = new PopupField<string>(dropdown.entries.Select(x => x.displayName).ToList(), name);
283
284 // Create anonymous lambda
285 EventCallback<ChangeEvent<string>> eventCallback = (evt) =>
286 {
287 subGraphNode.owner.owner.RegisterCompleteObjectUndo("Change Dropdown Value");
288 subGraphNode.SetDropdownEntryName(dropdown.referenceName, field.value);
289 subGraphNode.Dirty(ModificationScope.Topological);
290 };
291
292 field.RegisterValueChangedCallback(eventCallback);
293
294 // Setup so we can unregister this callback later
295 m_UnregisterAll += () => field.UnregisterValueChangedCallback(eventCallback);
296
297 m_DropdownItems.Add(new PropertyRow(new Label(dropdown.displayName)), (row) =>
298 {
299 row.styleSheets.Add(Resources.Load<StyleSheet>("Styles/PropertyRow"));
300 row.Add(field);
301 });
302 }
303 }
304 }
305 }
306
307 public VisualElement colorElement
308 {
309 get { return this; }
310 }
311
312 static readonly StyleColor noColor = new StyleColor(StyleKeyword.Null);
313 public void SetColor(Color color)
314 {
315 m_TitleContainer.style.borderBottomColor = color;
316 }
317
318 public void ResetColor()
319 {
320 m_TitleContainer.style.borderBottomColor = noColor;
321 }
322
323 public Color GetColor()
324 {
325 return m_TitleContainer.resolvedStyle.borderBottomColor;
326 }
327
328 void OnSubGraphDoubleClick(MouseDownEvent evt)
329 {
330 if (evt.clickCount == 2 && evt.button == 0)
331 {
332 SubGraphNode subgraphNode = node as SubGraphNode;
333
334 var path = AssetDatabase.GUIDToAssetPath(subgraphNode.subGraphGuid);
335 ShaderGraphImporterEditor.ShowGraphEditWindow(path);
336 }
337 }
338
339 public Node gvNode => this;
340
341 [Inspectable("Node", null)]
342 public AbstractMaterialNode node { get; private set; }
343
344 public override bool expanded
345 {
346 get => base.expanded;
347 set
348 {
349 if (base.expanded == value)
350 return;
351
352 base.expanded = value;
353
354 if (node.drawState.expanded != value)
355 {
356 var ds = node.drawState;
357 ds.expanded = value;
358 node.drawState = ds;
359 }
360
361 foreach (var inputPort in inputContainer.Query<ShaderPort>().ToList())
362 {
363 inputPort.parent.style.visibility = inputPort.style.visibility;
364 }
365
366 RefreshExpandedState(); // Necessary b/c we can't override enough Node.cs functions to update only what's needed
367 }
368 }
369
370 public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
371 {
372 if (evt.target is Node)
373 {
374 var canViewShader = node.hasPreview || node is SubGraphOutputNode;
375 evt.menu.AppendAction("Copy Shader", CopyToClipboard,
376 _ => canViewShader ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Hidden,
377 GenerationMode.ForReals);
378 evt.menu.AppendAction("Show Generated Code", ShowGeneratedCode,
379 _ => canViewShader ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Hidden,
380 GenerationMode.ForReals);
381
382 if (Unsupported.IsDeveloperMode())
383 {
384 evt.menu.AppendAction("Show Preview Code", ShowGeneratedCode,
385 _ => canViewShader ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Hidden,
386 GenerationMode.Preview);
387 }
388 }
389
390 base.BuildContextualMenu(evt);
391 }
392
393 void CopyToClipboard(DropdownMenuAction action)
394 {
395 GUIUtility.systemCopyBuffer = ConvertToShader((GenerationMode)action.userData);
396 }
397
398 public string SanitizeName(string name)
399 {
400 return new string(name.Where(c => !Char.IsWhiteSpace(c)).ToArray());
401 }
402
403 public void ShowGeneratedCode(DropdownMenuAction action)
404 {
405 string name = GetFirstAncestorOfType<GraphEditorView>().assetName;
406 var mode = (GenerationMode)action.userData;
407
408 string path = String.Format("Temp/GeneratedFromGraph-{0}-{1}-{2}{3}.shader", SanitizeName(name),
409 SanitizeName(node.name), node.objectId, mode == GenerationMode.Preview ? "-Preview" : "");
410 if (GraphUtil.WriteToFile(path, ConvertToShader(mode)))
411 GraphUtil.OpenFile(path);
412 }
413
414 string ConvertToShader(GenerationMode mode)
415 {
416 var generator = new Generator(node.owner, node, mode, node.name);
417 return generator.generatedShader;
418 }
419
420 void SetNodesAsDirty()
421 {
422 var editorView = GetFirstAncestorOfType<GraphEditorView>();
423 var nodeList = m_GraphView.Query<MaterialNodeView>().ToList();
424 editorView.colorManager.SetNodesDirty(nodeList);
425 }
426
427 void UpdateNodeViews()
428 {
429 var editorView = GetFirstAncestorOfType<GraphEditorView>();
430 var nodeList = m_GraphView.Query<MaterialNodeView>().ToList();
431 editorView.colorManager.UpdateNodeViews(nodeList);
432 }
433
434 public object GetObjectToInspect()
435 {
436 return node;
437 }
438
439 public void SupplyDataToPropertyDrawer(IPropertyDrawer propertyDrawer, Action inspectorUpdateDelegate)
440 {
441 if (propertyDrawer is IGetNodePropertyDrawerPropertyData nodePropertyDrawer)
442 {
443 nodePropertyDrawer.GetPropertyData(SetNodesAsDirty, UpdateNodeViews);
444 }
445 }
446
447 private void SetSelfSelected()
448 {
449 m_GraphView.ClearSelection();
450 m_GraphView.AddToSelection(this);
451 }
452
453 protected override void ToggleCollapse()
454 {
455 node.owner.owner.RegisterCompleteObjectUndo(!expanded ? "Expand Nodes" : "Collapse Nodes");
456 expanded = !expanded;
457
458 // If selected, expand/collapse the other applicable nodes that are also selected
459 if (selected)
460 {
461 m_GraphView.SetNodeExpandedForSelectedNodes(expanded, false);
462 }
463 }
464
465 void SetPreviewExpandedStateOnSelection(bool state)
466 {
467 // If selected, expand/collapse the other applicable nodes that are also selected
468 if (selected)
469 {
470 m_GraphView.SetPreviewExpandedForSelectedNodes(state);
471 }
472 else
473 {
474 node.owner.owner.RegisterCompleteObjectUndo(state ? "Expand Previews" : "Collapse Previews");
475 node.previewExpanded = state;
476 }
477 }
478
479 public bool CanToggleNodeExpanded()
480 {
481 return !(node is BlockNode) && m_CollapseButton.enabledInHierarchy;
482 }
483
484 void UpdatePreviewExpandedState(bool expanded)
485 {
486 node.previewExpanded = expanded;
487 if (m_PreviewFiller == null)
488 return;
489 if (expanded)
490 {
491 if (m_PreviewContainer.parent != this)
492 {
493 Add(m_PreviewContainer);
494 m_PreviewContainer.PlaceBehind(this.Q("selection-border"));
495 }
496 m_PreviewFiller.AddToClassList("expanded");
497 m_PreviewFiller.RemoveFromClassList("collapsed");
498 }
499 else
500 {
501 if (m_PreviewContainer.parent == m_PreviewFiller)
502 {
503 m_PreviewContainer.RemoveFromHierarchy();
504 }
505 m_PreviewFiller.RemoveFromClassList("expanded");
506 m_PreviewFiller.AddToClassList("collapsed");
507 }
508 UpdatePreviewTexture();
509 }
510
511 void UpdateTitle()
512 {
513 if (node is SubGraphNode subGraphNode && subGraphNode.asset != null)
514 title = subGraphNode.asset.name;
515 else
516 {
517 if (node.sgVersion < node.latestVersion)
518 {
519 if (node is IHasCustomDeprecationMessage customDeprecationMessage)
520 {
521 title = customDeprecationMessage.GetCustomDeprecationLabel();
522 }
523 else
524 {
525 title = node.name + $" (Legacy v{node.sgVersion})";
526 }
527 }
528 else
529 {
530 title = node.name;
531 }
532 }
533 }
534
535 void UpdateShaderPortsForSlots(bool inputSlots, List<MaterialSlot> allSlots, ShaderPort[] slotShaderPorts)
536 {
537 VisualElement portContainer = inputSlots ? inputContainer : outputContainer;
538 var existingPorts = portContainer.Query<ShaderPort>().ToList();
539 foreach (ShaderPort shaderPort in existingPorts)
540 {
541 var currentSlotId = shaderPort.slot.id;
542 int newSlotIndex = allSlots.FindIndex(s => s.id == currentSlotId);
543 if (newSlotIndex < 0)
544 {
545 // slot doesn't exist anymore, remove it
546 if (inputSlots)
547 portContainer.Remove(shaderPort.parent); // remove parent (includes the InputView)
548 else
549 portContainer.Remove(shaderPort);
550 }
551 else
552 {
553 var newSlot = allSlots[newSlotIndex];
554 slotShaderPorts[newSlotIndex] = shaderPort;
555
556 // these should probably be in an UpdateShaderPort(shaderPort, newSlot) function
557 shaderPort.slot = newSlot;
558 shaderPort.portName = newSlot.displayName;
559
560 if (inputSlots) // input slots also have to update the InputView
561 UpdatePortInputView(shaderPort);
562 }
563 }
564 }
565
566 public void OnModified(ModificationScope scope)
567 {
568 UpdateTitle();
569 SetActive(node.isActive);
570 if (node.hasPreview)
571 UpdatePreviewExpandedState(node.previewExpanded);
572
573 base.expanded = node.drawState.expanded;
574
575 switch (scope)
576 {
577 // Update slots to match node modification
578 case ModificationScope.Topological:
579 {
580 var slots = node.GetSlots<MaterialSlot>().ToList();
581 // going to record the corresponding ShaderPort to each slot, so we can order them later
582 ShaderPort[] slotShaderPorts = new ShaderPort[slots.Count];
583
584 // update existing input and output ports
585 UpdateShaderPortsForSlots(true, slots, slotShaderPorts);
586 UpdateShaderPortsForSlots(false, slots, slotShaderPorts);
587
588 // check if there are any new slots that must create new ports
589 for (int i = 0; i < slots.Count; i++)
590 {
591 if (slotShaderPorts[i] == null)
592 slotShaderPorts[i] = AddShaderPortForSlot(slots[i]);
593 }
594
595 // make sure they are in the right order
596 // by bringing each port to front in declaration order
597 // note that this sorts input and output containers at the same time
598 foreach (var shaderPort in slotShaderPorts)
599 {
600 if (shaderPort != null)
601 {
602 if (shaderPort.slot.isInputSlot)
603 shaderPort.parent.BringToFront();
604 else
605 shaderPort.BringToFront();
606 }
607 }
608
609 break;
610 }
611 }
612
613 RefreshExpandedState(); // Necessary b/c we can't override enough Node.cs functions to update only what's needed
614
615 foreach (var listener in m_ControlItems.Children().OfType<AbstractMaterialNodeModificationListener>())
616 {
617 if (listener != null)
618 listener.OnNodeModified(scope);
619 }
620 }
621
622 ShaderPort AddShaderPortForSlot(MaterialSlot slot)
623 {
624 if (slot.hidden)
625 return null;
626
627 ShaderPort port = ShaderPort.Create(slot, m_ConnectorListener);
628 if (slot.isOutputSlot)
629 {
630 outputContainer.Add(port);
631 }
632 else
633 {
634 var portContainer = new VisualElement();
635 portContainer.style.flexDirection = FlexDirection.Row;
636 var portInputView = new PortInputView(slot) { style = { position = Position.Absolute } };
637 portContainer.Add(portInputView);
638 portContainer.Add(port);
639 inputContainer.Add(portContainer);
640
641 // Update active state
642 if (node.isActive)
643 {
644 portInputView.RemoveFromClassList("disabled");
645 }
646 else
647 {
648 portInputView.AddToClassList("disabled");
649 }
650 }
651 port.OnDisconnect = OnEdgeDisconnected;
652
653 return port;
654 }
655
656 void AddSlots(IEnumerable<MaterialSlot> slots)
657 {
658 foreach (var slot in slots)
659 AddShaderPortForSlot(slot);
660 // Make sure the visuals are properly updated to reflect port list
661 RefreshPorts();
662 }
663
664 void OnEdgeDisconnected(Port obj)
665 {
666 RefreshExpandedState();
667 }
668
669 static bool GetPortInputView(ShaderPort port, out PortInputView view)
670 {
671 view = port.parent.Q<PortInputView>();
672 return view != null;
673 }
674
675 public void UpdatePortInputTypes()
676 {
677 var portList = inputContainer.Query<ShaderPort>().ToList();
678 portList.AddRange(outputContainer.Query<ShaderPort>().ToList());
679 foreach (var anchor in portList)
680 {
681 var slot = anchor.slot;
682 anchor.portName = slot.displayName;
683 anchor.visualClass = slot.concreteValueType.ToClassName();
684
685 if (GetPortInputView(anchor, out var portInputView))
686 {
687 portInputView.UpdateSlotType();
688 UpdatePortInputVisibility(portInputView, anchor);
689 }
690 }
691
692 foreach (var control in m_ControlItems.Children())
693 {
694 if (control is AbstractMaterialNodeModificationListener listener)
695 listener.OnNodeModified(ModificationScope.Graph);
696 }
697 }
698
699 void UpdatePortInputView(ShaderPort port)
700 {
701 if (GetPortInputView(port, out var portInputView))
702 {
703 portInputView.UpdateSlot(port.slot);
704 UpdatePortInputVisibility(portInputView, port);
705 }
706 }
707
708 void UpdatePortInputVisibility(PortInputView portInputView, ShaderPort port)
709 {
710 SetElementVisible(portInputView, !port.slot.isConnected);
711 port.parent.style.visibility = port.style.visibility;
712 portInputView.MarkDirtyRepaint();
713 }
714
715 void SetElementVisible(VisualElement element, bool isVisible)
716 {
717 const string k_HiddenClassList = "hidden";
718
719 if (isVisible)
720 {
721 // Restore default value for visibility by setting it to StyleKeyword.Null.
722 // Setting it to Visibility.Visible would make it visible even if parent is hidden.
723 element.style.visibility = StyleKeyword.Null;
724 element.RemoveFromClassList(k_HiddenClassList);
725 }
726 else
727 {
728 element.style.visibility = Visibility.Hidden;
729 element.AddToClassList(k_HiddenClassList);
730 }
731 }
732
733 SGBlackboardRow GetAssociatedBlackboardRow()
734 {
735 var graphEditorView = GetFirstAncestorOfType<GraphEditorView>();
736 if (graphEditorView == null)
737 return null;
738
739 var blackboardController = graphEditorView.blackboardController;
740 if (blackboardController == null)
741 return null;
742
743 if (node is KeywordNode keywordNode)
744 {
745 return blackboardController.GetBlackboardRow(keywordNode.keyword);
746 }
747
748 if (node is DropdownNode dropdownNode)
749 {
750 return blackboardController.GetBlackboardRow(dropdownNode.dropdown);
751 }
752 return null;
753 }
754
755 void OnMouseHover(EventBase evt)
756 {
757 // Keyword/Dropdown nodes should be highlighted when Blackboard entry is hovered
758 // TODO: Move to new NodeView type when keyword node has unique style
759 var blackboardRow = GetAssociatedBlackboardRow();
760 if (blackboardRow != null)
761 {
762 if (evt.eventTypeId == MouseEnterEvent.TypeId())
763 {
764 blackboardRow.AddToClassList("hovered");
765 }
766 else
767 {
768 blackboardRow.RemoveFromClassList("hovered");
769 }
770 }
771 }
772
773 void UpdatePreviewTexture()
774 {
775 if (m_PreviewRenderData.texture == null || !node.previewExpanded)
776 {
777 m_PreviewImage.visible = false;
778 m_PreviewImage.image = Texture2D.blackTexture;
779 }
780 else
781 {
782 m_PreviewImage.visible = true;
783 m_PreviewImage.AddToClassList("visible");
784 m_PreviewImage.RemoveFromClassList("hidden");
785 if (m_PreviewImage.image != m_PreviewRenderData.texture)
786 m_PreviewImage.image = m_PreviewRenderData.texture;
787 else
788 m_PreviewImage.MarkDirtyRepaint();
789
790 if (m_PreviewRenderData.shaderData.isOutOfDate)
791 m_PreviewImage.tintColor = new Color(1.0f, 1.0f, 1.0f, 0.3f);
792 else
793 m_PreviewImage.tintColor = Color.white;
794 }
795 }
796
797 public void Dispose()
798 {
799 ClearMessage();
800
801 foreach (var portInputView in inputContainer.Query<PortInputView>().ToList())
802 portInputView.Dispose();
803
804 foreach (var shaderPort in outputContainer.Query<ShaderPort>().ToList())
805 shaderPort.Dispose();
806
807 var propRow = GetAssociatedBlackboardRow();
808 // If this node view is deleted, remove highlighting from associated blackboard row
809 if (propRow != null)
810 {
811 propRow.RemoveFromClassList("hovered");
812 }
813
814 styleSheets.Clear();
815 inputContainer?.Clear();
816 outputContainer?.Clear();
817 m_DropdownsDivider?.Clear();
818 m_ControlsDivider?.Clear();
819 m_PreviewContainer?.Clear();
820 m_ControlItems?.Clear();
821
822 m_ConnectorListener = null;
823 m_GraphView = null;
824 m_DropdownItems = null;
825 m_ControlItems = null;
826 m_ControlsDivider = null;
827 m_PreviewContainer = null;
828 m_PreviewFiller = null;
829 m_PreviewImage = null;
830 m_DropdownsDivider = null;
831 m_TitleContainer = null;
832 node = null;
833 userData = null;
834
835 // Unregister callback
836 m_UnregisterAll?.Invoke();
837 m_UnregisterAll = null;
838
839 if (m_PreviewRenderData != null)
840 {
841 m_PreviewRenderData.onPreviewChanged -= UpdatePreviewTexture;
842 m_PreviewRenderData = null;
843 }
844 ShaderGraphPreferences.onAllowDeprecatedChanged -= UpdateTitle;
845
846 Clear();
847 }
848 }
849}