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.
274 lines
11 KiB
274 lines
11 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
using UnityEditor.Graphing;
|
|
using UnityEditorInternal;
|
|
|
|
namespace UnityEditor.ShaderGraph.Drawing
|
|
{
|
|
internal class ReorderableSlotListView : VisualElement
|
|
{
|
|
int m_SelectedIndex = -1;
|
|
SlotType m_SlotType;
|
|
GUIStyle m_LabelStyle;
|
|
IMGUIContainer m_Container;
|
|
AbstractMaterialNode m_Node;
|
|
ReorderableList m_ReorderableList;
|
|
bool m_AllowBareResources;
|
|
|
|
internal delegate void ListRecreatedDelegate();
|
|
ListRecreatedDelegate m_OnListRecreatedCallback = new ListRecreatedDelegate(() => { });
|
|
|
|
string label => string.Format("{0}s", m_SlotType.ToString());
|
|
|
|
public ReorderableList.AddCallbackDelegate OnAddCallback
|
|
{
|
|
get => m_ReorderableList?.onAddCallback;
|
|
set => m_ReorderableList.onAddCallback = value;
|
|
}
|
|
|
|
public ReorderableList.RemoveCallbackDelegate OnRemoveCallback
|
|
{
|
|
get => m_ReorderableList.onRemoveCallback;
|
|
set => m_ReorderableList.onRemoveCallback = value;
|
|
}
|
|
|
|
public ListRecreatedDelegate OnListRecreatedCallback
|
|
{
|
|
get => m_OnListRecreatedCallback;
|
|
set => m_OnListRecreatedCallback = value;
|
|
}
|
|
|
|
public Func<ConcreteSlotValueTypePopupName, bool> AllowedTypeCallback;
|
|
|
|
internal ReorderableSlotListView(AbstractMaterialNode node, SlotType slotType, bool allowBareResources)
|
|
{
|
|
m_AllowBareResources = allowBareResources;
|
|
styleSheets.Add(Resources.Load<StyleSheet>("Styles/ReorderableSlotListView"));
|
|
m_Node = node;
|
|
m_SlotType = slotType;
|
|
m_Container = new IMGUIContainer(() => OnGUIHandler()) { name = "ListContainer" };
|
|
Add(m_Container);
|
|
RecreateList();
|
|
AddCallbacks();
|
|
}
|
|
|
|
internal void RecreateList()
|
|
{
|
|
// Get slots based on type
|
|
List<MaterialSlot> slots = new List<MaterialSlot>();
|
|
if (m_SlotType == SlotType.Input)
|
|
m_Node.GetInputSlots(slots);
|
|
else
|
|
m_Node.GetOutputSlots(slots);
|
|
|
|
// Create reorderable list from IDs
|
|
List<int> slotIDs = slots.Select(s => s.id).ToList();
|
|
m_ReorderableList = new ReorderableList(slotIDs, typeof(int), true, true, true, true);
|
|
m_OnListRecreatedCallback();
|
|
}
|
|
|
|
private void OnGUIHandler()
|
|
{
|
|
if (m_ReorderableList == null)
|
|
{
|
|
RecreateList();
|
|
AddCallbacks();
|
|
}
|
|
|
|
using (var changeCheckScope = new EditorGUI.ChangeCheckScope())
|
|
{
|
|
m_ReorderableList.index = m_SelectedIndex;
|
|
m_ReorderableList.DoLayoutList();
|
|
|
|
if (changeCheckScope.changed)
|
|
m_Node.Dirty(ModificationScope.Node);
|
|
}
|
|
}
|
|
|
|
private void AddCallbacks()
|
|
{
|
|
m_ReorderableList.drawHeaderCallback = (Rect rect) =>
|
|
{
|
|
var labelRect = new Rect(rect.x, rect.y, rect.width - 10, rect.height);
|
|
EditorGUI.LabelField(labelRect, label);
|
|
};
|
|
|
|
// Draw Element
|
|
m_ReorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
|
|
{
|
|
// Slot is guaranteed to exist in this UI state
|
|
MaterialSlot oldSlot = m_Node.FindSlot<MaterialSlot>((int)m_ReorderableList.list[index]);
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
var displayName = EditorGUI.DelayedTextField(new Rect(rect.x, rect.y, rect.width / 2, EditorGUIUtility.singleLineHeight), oldSlot.RawDisplayName(), EditorStyles.label);
|
|
|
|
ConcreteSlotValueTypePopupName concreteValueTypePopupOrig =
|
|
oldSlot.concreteValueType.ToConcreteSlotValueTypePopupName(oldSlot.bareResource);
|
|
|
|
ConcreteSlotValueTypePopupName concreteValueTypePopupNew = (ConcreteSlotValueTypePopupName)EditorGUI.EnumPopup(
|
|
new Rect(rect.x + rect.width / 2, rect.y, rect.width - rect.width / 2, EditorGUIUtility.singleLineHeight),
|
|
GUIContent.none,
|
|
concreteValueTypePopupOrig,
|
|
e =>
|
|
{
|
|
ConcreteSlotValueTypePopupName csvtpn = (ConcreteSlotValueTypePopupName)e;
|
|
csvtpn.ToConcreteSlotValueType(out bool isBareResource);
|
|
if (isBareResource && !m_AllowBareResources)
|
|
return false;
|
|
return AllowedTypeCallback?.Invoke(csvtpn) ?? true;
|
|
}
|
|
);
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
m_Node.owner.owner.RegisterCompleteObjectUndo("Modify Port");
|
|
|
|
displayName = NodeUtils.ConvertToValidHLSLIdentifier(displayName);
|
|
|
|
if (displayName != oldSlot.RawDisplayName())
|
|
{
|
|
using (var tempSlots = PooledList<MaterialSlot>.Get())
|
|
{
|
|
m_Node.GetSlots(tempSlots);
|
|
|
|
// deduplicate against other slot shaderOutputNames
|
|
displayName = GraphUtil.DeduplicateName(tempSlots.Where(p => p.id != oldSlot.id).Select(p => p.shaderOutputName), "{0}_{1}", displayName);
|
|
}
|
|
}
|
|
|
|
ConcreteSlotValueType concreteValueType = concreteValueTypePopupNew.ToConcreteSlotValueType(out bool isBareResource);
|
|
|
|
// Because the type may have changed, we can't (always) just modify the existing slot. So create a new one and replace it.
|
|
var newSlot = MaterialSlot.CreateMaterialSlot(concreteValueType.ToSlotValueType(), oldSlot.id, displayName, displayName, m_SlotType, Vector4.zero);
|
|
newSlot.CopyValuesFrom(oldSlot);
|
|
m_Node.AddSlot(newSlot, false);
|
|
newSlot.bareResource = isBareResource;
|
|
|
|
List<int> orderedSlotIds = new List<int>();
|
|
if (m_SlotType == SlotType.Input)
|
|
{
|
|
orderedSlotIds.AddRange(m_ReorderableList.list.OfType<int>());
|
|
|
|
List<MaterialSlot> slots = new List<MaterialSlot>();
|
|
m_Node.GetOutputSlots(slots);
|
|
orderedSlotIds.AddRange(slots.Select(s => s.id));
|
|
}
|
|
else
|
|
{
|
|
List<MaterialSlot> slots = new List<MaterialSlot>();
|
|
m_Node.GetInputSlots(slots);
|
|
orderedSlotIds.AddRange(slots.Select(s => s.id));
|
|
|
|
orderedSlotIds.AddRange(m_ReorderableList.list.OfType<int>());
|
|
}
|
|
|
|
m_Node.SetSlotOrder(orderedSlotIds);
|
|
|
|
RecreateList();
|
|
m_Node.ValidateNode();
|
|
}
|
|
};
|
|
|
|
// Element height
|
|
m_ReorderableList.elementHeightCallback = (int indexer) =>
|
|
{
|
|
return m_ReorderableList.elementHeight;
|
|
};
|
|
|
|
// Add callback delegates
|
|
m_ReorderableList.onSelectCallback += SelectEntry;
|
|
m_ReorderableList.onAddCallback += AddEntry;
|
|
m_ReorderableList.onRemoveCallback += RemoveEntry;
|
|
m_ReorderableList.onReorderCallback += ReorderEntries;
|
|
}
|
|
|
|
private void SelectEntry(ReorderableList list)
|
|
{
|
|
m_SelectedIndex = list.index;
|
|
}
|
|
|
|
private void AddEntry(ReorderableList list)
|
|
{
|
|
m_Node.owner.owner.RegisterCompleteObjectUndo("Add Port");
|
|
// Need to get all current slots to get the next valid ID
|
|
List<MaterialSlot> slots = new List<MaterialSlot>();
|
|
m_Node.GetSlots(slots);
|
|
int[] slotIDs = slots.Select(s => s.id).OrderByDescending(s => s).ToArray();
|
|
int newSlotID = slotIDs.Length > 0 ? slotIDs[0] + 1 : 0;
|
|
|
|
string name = NodeUtils.GetDuplicateSafeNameForSlot(m_Node, newSlotID, "New");
|
|
|
|
// Create a new slot and add it
|
|
var newSlot = MaterialSlot.CreateMaterialSlot(SlotValueType.Vector1, newSlotID, name, NodeUtils.GetHLSLSafeName(name), m_SlotType, Vector4.zero);
|
|
m_Node.AddSlot(newSlot);
|
|
|
|
// Select the new slot, then validate the node
|
|
m_SelectedIndex = list.list.Count - 1;
|
|
m_Node.ValidateNode();
|
|
}
|
|
|
|
private void RemoveEntry(ReorderableList list)
|
|
{
|
|
m_Node.owner.owner.RegisterCompleteObjectUndo("Remove Port");
|
|
|
|
// Remove the slot from the node
|
|
m_SelectedIndex = list.index;
|
|
m_Node.RemoveSlot((int)m_ReorderableList.list[m_SelectedIndex]);
|
|
|
|
// Then remove it from the list
|
|
// Need to do this order to preserve the list indicies for previous step
|
|
ReorderableList.defaultBehaviours.DoRemoveButton(list);
|
|
|
|
// Validate
|
|
m_Node.ValidateNode();
|
|
}
|
|
|
|
private void ReorderEntries(ReorderableList list)
|
|
{
|
|
m_Node.owner.owner.RegisterCompleteObjectUndo("Reorder Ports");
|
|
|
|
// Get all the current slots
|
|
List<MaterialSlot> slots = new List<MaterialSlot>();
|
|
if (m_SlotType == SlotType.Input)
|
|
m_Node.GetInputSlots<MaterialSlot>(slots);
|
|
else
|
|
m_Node.GetOutputSlots<MaterialSlot>(slots);
|
|
|
|
// Store the edges
|
|
Dictionary<MaterialSlot, List<IEdge>> edgeDict = new Dictionary<MaterialSlot, List<IEdge>>();
|
|
foreach (MaterialSlot slot in slots)
|
|
edgeDict.Add(slot, (List<IEdge>)slot.owner.owner.GetEdges(slot.slotReference));
|
|
|
|
// Get reorder slots so need to remove them all then re-add
|
|
foreach (MaterialSlot slot in slots)
|
|
m_Node.RemoveSlot(slot.id);
|
|
|
|
// Order them by their slot ID
|
|
slots = slots.OrderBy(s => s.id).ToList();
|
|
|
|
// Now add the slots back based on the list order
|
|
// For each list entry get the slot with that ID
|
|
for (int i = 0; i < list.list.Count; i++)
|
|
{
|
|
var currentSlot = slots.Where(s => s.id == (int)list.list[i]).FirstOrDefault();
|
|
m_Node.AddSlot(currentSlot);
|
|
}
|
|
|
|
// Reconnect the edges
|
|
foreach (KeyValuePair<MaterialSlot, List<IEdge>> entry in edgeDict)
|
|
{
|
|
foreach (IEdge edge in entry.Value)
|
|
{
|
|
m_Node.owner.Connect(edge.outputSlot, edge.inputSlot);
|
|
}
|
|
}
|
|
|
|
RecreateList();
|
|
m_Node.ValidateNode();
|
|
}
|
|
}
|
|
}
|