You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
15 KiB
387 lines
15 KiB
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); |
|
} |
|
} |
|
}
|
|
|