Singularity/Library/PackageCache/com.unity.shadergraph@12.1.11/Editor/Drawing/Blackboard/SGBlackboardField.cs
2024-05-06 11:45:45 -07:00

388 lines
15 KiB
C#

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>
{
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;
// Update the Pill text if shader input name is changed
// we handle this in controller if we change it through SGBlackboardField, but its possible to change through PropertyNodeView as well
shaderInput.displayNameUpdateTrigger += newDisplayName => text = newDisplayName;
// 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);
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(); });
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);
}
}
~SGBlackboardField()
{
ShaderGraphPreferences.onAllowDeprecatedChanged -= UpdateTypeText;
}
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.PreventDefault();
}
else
{
e.StopPropagation();
}
}
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);
}
}
}