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}