123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Reflection;
- using UnityEngine;
- using UnityEngine.UIElements;
- using UnityEditor.Graphing;
- using UnityEditor.Experimental.GraphView;
- using UnityEditor.ShaderGraph.Drawing.Controls;
- using UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers;
- using UnityEditor.ShaderGraph.Internal;
- using UnityEngine.Assertions;
-
- using ContextualMenuManipulator = UnityEngine.UIElements.ContextualMenuManipulator;
- using GraphDataStore = UnityEditor.ShaderGraph.DataStore<UnityEditor.ShaderGraph.GraphData>;
-
- namespace UnityEditor.ShaderGraph.Drawing
- {
- class SGBlackboardField : GraphElement, IInspectable, ISGControlledElement<ShaderInputViewController>, IDisposable
- {
- static readonly Texture2D k_ExposedIcon = Resources.Load<Texture2D>("GraphView/Nodes/BlackboardFieldExposed");
- static readonly string k_UxmlTemplatePath = "UXML/Blackboard/SGBlackboardField";
- static readonly string k_StyleSheetPath = "Styles/SGBlackboard";
-
- ShaderInputViewModel m_ViewModel;
-
- ShaderInputViewModel ViewModel
- {
- get => m_ViewModel;
- set => m_ViewModel = value;
- }
-
- VisualElement m_ContentItem;
- Pill m_Pill;
- Label m_TypeLabel;
- TextField m_TextField;
- internal TextField textField => m_TextField;
-
- Action m_ResetReferenceNameTrigger;
- List<Node> m_SelectedNodes = new List<Node>();
-
- public string text
- {
- get { return m_Pill.text; }
- set { m_Pill.text = value; }
- }
-
- public string typeText
- {
- get { return m_TypeLabel.text; }
- set { m_TypeLabel.text = value; }
- }
-
- public Texture icon
- {
- get { return m_Pill.icon; }
- set { m_Pill.icon = value; }
- }
-
- public bool highlighted
- {
- get { return m_Pill.highlighted; }
- set { m_Pill.highlighted = value; }
- }
-
- internal SGBlackboardField(ShaderInputViewModel viewModel)
- {
- ViewModel = viewModel;
- // Store ShaderInput in userData object
- userData = ViewModel.model;
- if (userData == null)
- {
- AssertHelpers.Fail("Could not initialize blackboard field as shader input was null.");
- return;
- }
- // Store the Model guid as viewDataKey as that is persistent
- viewDataKey = ViewModel.model.guid.ToString();
-
- var visualTreeAsset = Resources.Load<VisualTreeAsset>(k_UxmlTemplatePath);
- Assert.IsNotNull(visualTreeAsset);
-
- VisualElement mainContainer = visualTreeAsset.Instantiate();
- var styleSheet = Resources.Load<StyleSheet>(k_StyleSheetPath);
- Assert.IsNotNull(styleSheet);
- styleSheets.Add(styleSheet);
-
- mainContainer.AddToClassList("mainContainer");
- mainContainer.pickingMode = PickingMode.Ignore;
-
- m_ContentItem = mainContainer.Q("contentItem");
- m_Pill = mainContainer.Q<Pill>("pill");
- m_TypeLabel = mainContainer.Q<Label>("typeLabel");
- m_TextField = mainContainer.Q<TextField>("textField");
- m_TextField.style.display = DisplayStyle.None;
-
- // Handles the upgrade fix for the old color property deprecation
- if (shaderInput is AbstractShaderProperty property)
- {
- property.onAfterVersionChange += () =>
- {
- this.typeText = property.GetPropertyTypeString();
- this.m_InspectorUpdateDelegate();
- };
- }
-
- Add(mainContainer);
-
- RegisterCallback<MouseDownEvent>(OnMouseDownEvent);
-
- // setting Capabilities.Selectable adds a ClickSelector
- capabilities |= Capabilities.Selectable | Capabilities.Droppable | Capabilities.Deletable | Capabilities.Renamable;
-
- ClearClassList();
- AddToClassList("blackboardField");
-
- this.name = "SGBlackboardField";
- UpdateFromViewModel();
-
- // add the right click context menu
- IManipulator contextMenuManipulator = new ContextualMenuManipulator(AddContextMenuOptions);
- this.AddManipulator(contextMenuManipulator);
- this.AddManipulator(new SelectionDropper());
- this.AddManipulator(new ContextualMenuManipulator(BuildFieldContextualMenu));
-
- // When a display name is changed through the BlackboardPill, bind this callback to handle it with appropriate change action
- var textInputElement = m_TextField.Q(TextField.textInputUssName);
- textInputElement.RegisterCallback<FocusOutEvent>(e => { OnEditTextFinished(); }, TrickleDown.TrickleDown);
-
- ShaderGraphPreferences.onAllowDeprecatedChanged += UpdateTypeText;
-
- RegisterCallback<MouseEnterEvent>(evt => OnMouseHover(evt, ViewModel.model));
- RegisterCallback<MouseLeaveEvent>(evt => OnMouseHover(evt, ViewModel.model));
- RegisterCallback<DragUpdatedEvent>(OnDragUpdatedEvent);
-
- var blackboard = ViewModel.parentView.GetFirstAncestorOfType<SGBlackboard>();
- if (blackboard != null)
- {
- // These callbacks are used for the property dragging scroll behavior
- RegisterCallback<DragEnterEvent>(blackboard.OnDragEnterEvent);
- RegisterCallback<DragExitedEvent>(blackboard.OnDragExitedEvent);
-
- // These callbacks are used for the property dragging scroll behavior
- RegisterCallback<DragEnterEvent>(blackboard.OnDragEnterEvent);
- RegisterCallback<DragExitedEvent>(blackboard.OnDragExitedEvent);
- }
- }
-
- void AddContextMenuOptions(ContextualMenuPopulateEvent evt)
- {
- // Checks if the reference name has been overridden and appends menu action to reset it, if so
- if (shaderInput.isRenamable &&
- !string.IsNullOrEmpty(shaderInput.overrideReferenceName))
- {
- evt.menu.AppendAction(
- "Reset Reference",
- e =>
- {
- var resetReferenceNameAction = new ResetReferenceNameAction();
- resetReferenceNameAction.shaderInputReference = shaderInput;
- ViewModel.requestModelChangeAction(resetReferenceNameAction);
- m_ResetReferenceNameTrigger();
- },
- DropdownMenuAction.AlwaysEnabled);
- }
-
- if (shaderInput is ColorShaderProperty colorProp)
- {
- PropertyNodeView.AddMainColorMenuOptions(evt, colorProp, controller.graphData, m_InspectorUpdateDelegate);
- }
-
-
- if (shaderInput is Texture2DShaderProperty texProp)
- {
- PropertyNodeView.AddMainTextureMenuOptions(evt, texProp, controller.graphData, m_InspectorUpdateDelegate);
- }
- }
-
- internal void UpdateFromViewModel()
- {
- this.text = ViewModel.inputName;
- this.icon = ViewModel.isInputExposed ? k_ExposedIcon : null;
- this.typeText = ViewModel.inputTypeName;
- }
-
- ShaderInputViewController m_Controller;
-
- // --- Begin ISGControlledElement implementation
- public void OnControllerChanged(ref SGControllerChangedEvent e)
- {
- }
-
- public void OnControllerEvent(SGControllerEvent e)
- {
- }
-
- public ShaderInputViewController controller
- {
- get => m_Controller;
- set
- {
- if (m_Controller != value)
- {
- if (m_Controller != null)
- {
- m_Controller.UnregisterHandler(this);
- }
-
- m_Controller = value;
-
- if (m_Controller != null)
- {
- m_Controller.RegisterHandler(this);
- }
- }
- }
- }
-
- SGController ISGControlledElement.controller => m_Controller;
-
- // --- ISGControlledElement implementation
-
- [Inspectable("Shader Input", null)]
- public ShaderInput shaderInput => ViewModel.model;
-
- public string inspectorTitle => ViewModel.inputName + " " + ViewModel.inputTypeName;
-
- public object GetObjectToInspect()
- {
- return shaderInput;
- }
-
- Action m_InspectorUpdateDelegate;
-
- public void SupplyDataToPropertyDrawer(IPropertyDrawer propertyDrawer, Action inspectorUpdateDelegate)
- {
- if (propertyDrawer is ShaderInputPropertyDrawer shaderInputPropertyDrawer)
- {
- // We currently need to do a halfway measure between the old way of handling stuff for property drawers (how FieldView and NodeView handle it)
- // and how we want to handle it with the new style of controllers and views. Ideally we'd just hand the property drawer a view model and thats it.
- // We've maintained all the old callbacks as they are in the PropertyDrawer to reduce possible halo changes and support PropertyNodeView functionality
- // Instead we supply different underlying methods for the callbacks in the new SGBlackboardField,
- // that way both code paths should work until we can refactor PropertyNodeView
- shaderInputPropertyDrawer.GetViewModel(
- ViewModel,
- controller.graphData,
- ((triggerInspectorUpdate, modificationScope) =>
- {
- controller.DirtyNodes(modificationScope);
- if (triggerInspectorUpdate)
- inspectorUpdateDelegate();
- }));
-
- m_ResetReferenceNameTrigger = shaderInputPropertyDrawer.ResetReferenceName;
- m_InspectorUpdateDelegate = inspectorUpdateDelegate;
- }
- }
-
- void OnMouseDownEvent(MouseDownEvent e)
- {
- if ((e.clickCount == 2) && e.button == (int)MouseButton.LeftMouse && IsRenamable())
- {
- OpenTextEditor();
- e.StopPropagation();
- focusController.IgnoreEvent(e);
- }
- }
-
- void OnDragUpdatedEvent(DragUpdatedEvent evt)
- {
- if (m_SelectedNodes.Any())
- {
- foreach (var node in m_SelectedNodes)
- {
- node.RemoveFromClassList("hovered");
- }
- m_SelectedNodes.Clear();
- }
- }
-
- // TODO: Move to controller? Feels weird for this to be directly communicating with PropertyNodes etc.
- // Better way would be to send event to controller that notified of hover enter/exit and have other controllers be sent those events in turn
- void OnMouseHover(EventBase evt, ShaderInput input)
- {
- var graphView = ViewModel.parentView.GetFirstAncestorOfType<MaterialGraphView>();
- if (evt.eventTypeId == MouseEnterEvent.TypeId())
- {
- foreach (var node in graphView.nodes.ToList())
- {
- if (input is AbstractShaderProperty property)
- {
- if (node.userData is PropertyNode propertyNode)
- {
- if (propertyNode.property == input)
- {
- m_SelectedNodes.Add(node);
- node.AddToClassList("hovered");
- }
- }
- }
- else if (input is ShaderKeyword keyword)
- {
- if (node.userData is KeywordNode keywordNode)
- {
- if (keywordNode.keyword == input)
- {
- m_SelectedNodes.Add(node);
- node.AddToClassList("hovered");
- }
- }
- }
- else if (input is ShaderDropdown dropdown)
- {
- if (node.userData is DropdownNode dropdownNode)
- {
- if (dropdownNode.dropdown == input)
- {
- m_SelectedNodes.Add(node);
- node.AddToClassList("hovered");
- }
- }
- }
- }
- }
- else if (evt.eventTypeId == MouseLeaveEvent.TypeId() && m_SelectedNodes.Any())
- {
- foreach (var node in m_SelectedNodes)
- {
- node.RemoveFromClassList("hovered");
- }
- m_SelectedNodes.Clear();
- }
- }
-
- void UpdateTypeText()
- {
- if (shaderInput is AbstractShaderProperty asp)
- {
- typeText = asp.GetPropertyTypeString();
- }
- }
-
- internal void OpenTextEditor()
- {
- m_TextField.SetValueWithoutNotify(text);
- m_TextField.style.display = DisplayStyle.Flex;
- m_ContentItem.visible = false;
- m_TextField.Q(TextField.textInputUssName).Focus();
- m_TextField.SelectAll();
- }
-
- void OnEditTextFinished()
- {
- m_ContentItem.visible = true;
- m_TextField.style.display = DisplayStyle.None;
-
- if (text != m_TextField.text && String.IsNullOrWhiteSpace(m_TextField.text) == false && String.IsNullOrEmpty(m_TextField.text) == false)
- {
- var changeDisplayNameAction = new ChangeDisplayNameAction();
- changeDisplayNameAction.shaderInputReference = shaderInput;
- changeDisplayNameAction.newDisplayNameValue = m_TextField.text;
- ViewModel.requestModelChangeAction(changeDisplayNameAction);
- m_InspectorUpdateDelegate?.Invoke();
- }
- else
- {
- // Reset text field to original name
- m_TextField.value = text;
- }
- }
-
- protected virtual void BuildFieldContextualMenu(ContextualMenuPopulateEvent evt)
- {
- evt.menu.AppendAction("Rename", (a) => OpenTextEditor(), DropdownMenuAction.AlwaysEnabled);
- }
-
- public void Dispose()
- {
- m_ResetReferenceNameTrigger = null;
- m_InspectorUpdateDelegate = null;
-
- UnregisterCallback<MouseDownEvent>(OnMouseDownEvent);
- UnregisterCallback<MouseEnterEvent>(evt => OnMouseHover(evt, ViewModel.model));
- UnregisterCallback<MouseLeaveEvent>(evt => OnMouseHover(evt, ViewModel.model));
- UnregisterCallback<DragUpdatedEvent>(OnDragUpdatedEvent);
- var blackboard = ViewModel.parentView.GetFirstAncestorOfType<SGBlackboard>();
- UnregisterCallback<DragEnterEvent>(blackboard.OnDragEnterEvent);
- UnregisterCallback<DragExitedEvent>(blackboard.OnDragExitedEvent);
- ShaderGraphPreferences.onAllowDeprecatedChanged -= UpdateTypeText;
-
- // Clear references
- m_SelectedNodes = null;
- m_ContentItem = null;
- m_Pill = null;
- m_TypeLabel = null;
- m_TextField = null;
- m_Controller = null;
- m_ViewModel = null;
- userData = null;
- styleSheets.Clear();
- Clear();
- }
- }
- }
|