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.
 
 
 
 

359 lines
15 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor.Experimental.GraphView;
using UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers;
using UnityEditor.ShaderGraph.Drawing.Views;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Inspector
{
class InspectorView : GraphSubWindow
{
const float k_InspectorUpdateInterval = 0.25f;
const int k_InspectorElementLimit = 20;
bool m_GraphSettingsTabFocused = false;
int m_CurrentlyInspectedElementsCount = 0;
readonly List<Type> m_PropertyDrawerList = new List<Type>();
HashSet<IInspectable> cachedInspectables = new();
// There's persistent data that is stored in the graph settings property drawer that we need to hold onto between interactions
IPropertyDrawer m_graphSettingsPropertyDrawer = new GraphDataPropertyDrawer();
public override string windowTitle => "Graph Inspector";
public override string elementName => "InspectorView";
public override string styleName => "InspectorView";
public override string UxmlName => "GraphInspector";
public override string layoutKey => "UnityEditor.ShaderGraph.InspectorWindow";
TabView m_GraphInspectorView;
Tab m_GraphSettingsTab;
Tab m_NodeSettingsTab;
protected VisualElement m_GraphSettingsContainer;
protected VisualElement m_NodeSettingsContainer;
List<IPropertyDrawer> m_AllActivePropertyDrawers = new List<IPropertyDrawer>();
Label m_MaxItemsMessageLabel;
void RegisterPropertyDrawer(Type newPropertyDrawerType)
{
if (typeof(IPropertyDrawer).IsAssignableFrom(newPropertyDrawerType) == false)
{
Debug.Log("Attempted to register a property drawer that doesn't inherit from IPropertyDrawer!");
return;
}
var newPropertyDrawerAttribute = newPropertyDrawerType.GetCustomAttribute<SGPropertyDrawerAttribute>();
if (newPropertyDrawerAttribute != null)
{
foreach (var existingPropertyDrawerType in m_PropertyDrawerList)
{
var existingPropertyDrawerAttribute = existingPropertyDrawerType.GetCustomAttribute<SGPropertyDrawerAttribute>();
if (newPropertyDrawerAttribute.propertyType.IsSubclassOf(existingPropertyDrawerAttribute.propertyType))
{
// Derived types need to be at start of list
m_PropertyDrawerList.Insert(0, newPropertyDrawerType);
return;
}
if (existingPropertyDrawerAttribute.propertyType.IsSubclassOf(newPropertyDrawerAttribute.propertyType))
{
// Add new base class type to end of list
m_PropertyDrawerList.Add(newPropertyDrawerType);
// Shift already added existing type to the beginning of the list
m_PropertyDrawerList.Remove(existingPropertyDrawerType);
m_PropertyDrawerList.Insert(0, existingPropertyDrawerType);
return;
}
}
m_PropertyDrawerList.Add(newPropertyDrawerType);
}
else
Debug.Log("Attempted to register property drawer: " + newPropertyDrawerType + " that isn't marked up with the SGPropertyDrawer attribute!");
}
public InspectorView(InspectorViewModel viewModel) : base(viewModel)
{
m_GraphInspectorView = m_MainContainer.Q<TabView>("GraphInspectorView");
m_GraphSettingsTab = m_GraphInspectorView.Q<Tab>("GraphSettingsTab");
m_NodeSettingsTab = m_GraphInspectorView.Q<Tab>("NodeSettingsTab");
m_GraphSettingsContainer = m_GraphInspectorView.Q<VisualElement>("GraphSettingsContainer");
m_NodeSettingsContainer = m_GraphInspectorView.Q<VisualElement>("NodeSettingsContainer");
m_MaxItemsMessageLabel = m_GraphInspectorView.Q<Label>("maxItemsMessageLabel");
m_ContentContainer.Add(m_GraphInspectorView);
m_ScrollView = this.Q<ScrollView>();
m_GraphSettingsTab.selected += GraphSettingsTabClicked;
m_NodeSettingsTab.selected += NodeSettingsTabClicked;
isWindowScrollable = true;
isWindowResizable = true;
var unregisteredPropertyDrawerTypes = TypeCache.GetTypesDerivedFrom<IPropertyDrawer>().ToList();
foreach (var type in unregisteredPropertyDrawerTypes)
{
RegisterPropertyDrawer(type);
}
// By default at startup, show graph settings
m_GraphInspectorView.activeTab = m_GraphSettingsTab;
}
void GraphSettingsTabClicked(Tab _)
{
m_GraphSettingsTabFocused = true;
}
void NodeSettingsTabClicked(Tab _)
{
m_GraphSettingsTabFocused = false;
}
public void InitializeGraphSettings()
{
ShowGraphSettings_Internal(m_GraphSettingsContainer);
}
public bool doesInspectorNeedUpdate { get; set; }
public void TriggerInspectorUpdate(IEnumerable<ISelectable> selectionList)
{
// An optimization that prevents inspector updates from getting triggered every time a selection event is issued in the event of large selections
if (selectionList?.Count() > k_InspectorElementLimit)
return;
doesInspectorNeedUpdate = true;
}
public void Update()
{
// Tear down all existing active property drawers, everything is getting rebuilt
foreach (IPropertyDrawer propDrawer in m_AllActivePropertyDrawers)
propDrawer.DisposePropertyDrawer();
m_AllActivePropertyDrawers.Clear();
ShowGraphSettings_Internal(m_GraphSettingsContainer);
m_NodeSettingsContainer.Clear();
try
{
bool anySelectables = false;
int currentInspectablesCount = 0;
var currentInspectables = new HashSet<IInspectable>();
foreach (var selectable in selection)
{
if (selectable is IInspectable inspectable)
{
DrawInspectable(m_NodeSettingsContainer, inspectable);
currentInspectablesCount++;
anySelectables = true;
currentInspectables.Add(inspectable);
}
if (currentInspectablesCount == k_InspectorElementLimit)
{
m_NodeSettingsContainer.Add(m_MaxItemsMessageLabel);
m_MaxItemsMessageLabel.style.visibility = Visibility.Visible;
break;
}
}
// If we have changed our inspector selection while the graph settings tab was focused, we want to switch back to the node settings tab, so invalidate the flag
foreach (var currentInspectable in currentInspectables)
{
if (cachedInspectables.Contains(currentInspectable) == false)
m_GraphSettingsTabFocused = false;
}
cachedInspectables = currentInspectables;
m_CurrentlyInspectedElementsCount = currentInspectablesCount;
if (anySelectables && !m_GraphSettingsTabFocused)
{
// Anything selectable in the graph (GraphSettings not included) is only ever interacted with through the
// Node Settings tab so we can make the assumption they want to see that tab
m_GraphInspectorView.activeTab = m_NodeSettingsTab;
}
}
catch (Exception e)
{
Debug.LogError(e);
}
if (doesInspectorNeedUpdate)
doesInspectorNeedUpdate = false;
m_NodeSettingsContainer.MarkDirtyRepaint();
}
public void RefreshInspectables()
{
// And redraw the inspector
doesInspectorNeedUpdate = true;
Update();
}
private static void FindChildrenRecursive<T>(VisualElement element, List<T> results)
{
foreach (var child in element.Children())
{
if (child is T tChild)
results.Add(tChild);
FindChildrenRecursive(child, results);
}
}
void DrawInspectable(
VisualElement outputVisualElement,
IInspectable inspectable,
IPropertyDrawer propertyDrawerToUse = null)
{
InspectorUtils.GatherInspectorContent(m_PropertyDrawerList, outputVisualElement, inspectable, TriggerInspectorUpdate, m_AllActivePropertyDrawers, propertyDrawerToUse);
}
internal void HandleGraphChanges()
{
float timePassed = (float)(EditorApplication.timeSinceStartup % k_InspectorUpdateInterval);
int currentInspectablesCount = 0;
foreach (var selectable in selection)
{
if (selectable is IInspectable)
currentInspectablesCount++;
}
// Don't update for selections beyond a certain amount as they are no longer visible in the inspector past a certain point and only cost performance as the user performs operations
if (timePassed < 0.01f && selection.Count < k_InspectorElementLimit && currentInspectablesCount != m_CurrentlyInspectedElementsCount)
{
m_GraphSettingsTabFocused = false;
Update();
}
}
void TriggerInspectorUpdate()
{
Update();
}
public override void Dispose()
{
m_AllActivePropertyDrawers.Clear();
m_PropertyDrawerList.Clear();
m_graphSettingsPropertyDrawer = null;
m_GraphInspectorView.Q<Tab>("GraphSettingsTab").selected -= GraphSettingsTabClicked;
m_GraphInspectorView.Q<Tab>("NodeSettingsTab").selected -= NodeSettingsTabClicked;
m_GraphInspectorView = null;
m_GraphSettingsContainer = null;
m_NodeSettingsContainer = null;
m_MaxItemsMessageLabel = null;
}
// This should be implemented by any inspector class that wants to define its own GraphSettings
// which for SG, is a representation of the settings in GraphData
protected virtual void ShowGraphSettings_Internal(VisualElement contentContainer)
{
contentContainer.Clear();
var graphEditorView = ParentView.GetFirstAncestorOfType<GraphEditorView>();
if (graphEditorView == null)
return;
DrawInspectable(contentContainer, (IInspectable)ParentView, m_graphSettingsPropertyDrawer);
contentContainer.MarkDirtyRepaint();
}
}
public static class InspectorUtils
{
internal static void GatherInspectorContent(
List<Type> propertyDrawerList,
VisualElement outputVisualElement,
IInspectable inspectable,
Action propertyChangeCallback,
List<IPropertyDrawer> allPropertyDrawerInstances,
IPropertyDrawer propertyDrawerToUse = null)
{
var dataObject = inspectable.GetObjectToInspect();
if (dataObject == null)
throw new NullReferenceException("DataObject returned by Inspectable is null!");
var properties = inspectable.GetType().GetProperties(BindingFlags.Default | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (properties == null)
throw new NullReferenceException("PropertyInfos returned by Inspectable is null!");
foreach (var propertyInfo in properties)
{
var attribute = propertyInfo.GetCustomAttribute<InspectableAttribute>();
if (attribute == null)
continue;
var propertyType = propertyInfo.GetGetMethod(true).Invoke(inspectable, new object[] { }).GetType();
var propertyDrawerInstance = propertyDrawerToUse;
if (propertyDrawerInstance == null)
{
if (IsPropertyTypeHandled(propertyDrawerList, propertyType, out var propertyDrawerTypeToUse))
propertyDrawerInstance = (IPropertyDrawer)Activator.CreateInstance(propertyDrawerTypeToUse);
}
if (propertyDrawerInstance != null)
{
// Assign the inspector update delegate so any property drawer can trigger an inspector update if it needs it
propertyDrawerInstance.inspectorUpdateDelegate = propertyChangeCallback;
// Supply any required data to this particular kind of property drawer
inspectable.SupplyDataToPropertyDrawer(propertyDrawerInstance, propertyChangeCallback);
var propertyGUI = propertyDrawerInstance.DrawProperty(propertyInfo, dataObject, attribute);
outputVisualElement.Add(propertyGUI);
if (allPropertyDrawerInstances != null)
allPropertyDrawerInstances.Add(propertyDrawerInstance);
}
}
}
static bool IsPropertyTypeHandled(
List<Type> propertyDrawerList,
Type typeOfProperty,
out Type propertyDrawerToUse)
{
propertyDrawerToUse = null;
// Check to see if a property drawer has been registered that handles this type
foreach (var propertyDrawerType in propertyDrawerList)
{
var typeHandledByPropertyDrawer = propertyDrawerType.GetCustomAttribute<SGPropertyDrawerAttribute>();
// Numeric types and boolean wrapper types like ToggleData handled here
if (typeHandledByPropertyDrawer.propertyType == typeOfProperty)
{
propertyDrawerToUse = propertyDrawerType;
return true;
}
// Generics and Enumerable types are handled here
else if (typeHandledByPropertyDrawer.propertyType.IsAssignableFrom(typeOfProperty))
{
// Before returning it, check for a more appropriate type further
propertyDrawerToUse = propertyDrawerType;
return true;
}
// Enums are weird and need to be handled explicitly as done below as their runtime type isn't the same as System.Enum
else if (typeHandledByPropertyDrawer.propertyType == typeOfProperty.BaseType)
{
propertyDrawerToUse = propertyDrawerType;
return true;
}
}
return false;
}
}
}