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.
818 lines
37 KiB
818 lines
37 KiB
using System.Collections.Generic; |
|
using System.Linq; |
|
using UnityEngine; |
|
using UnityEditor.Experimental.GraphView; |
|
using UnityEngine.UIElements; |
|
using System; |
|
using UnityEditor.Graphing; |
|
using UnityEditor.ShaderGraph.Internal; |
|
using GraphDataStore = UnityEditor.ShaderGraph.DataStore<UnityEditor.ShaderGraph.GraphData>; |
|
using BlackboardItem = UnityEditor.ShaderGraph.Internal.ShaderInput; |
|
|
|
namespace UnityEditor.ShaderGraph.Drawing |
|
{ |
|
struct BlackboardShaderInputOrder |
|
{ |
|
public bool isKeyword; |
|
public bool isDropdown; |
|
public KeywordType keywordType; |
|
public ShaderKeyword builtInKeyword; |
|
public string deprecatedPropertyName; |
|
public int version; |
|
} |
|
class BlackboardShaderInputFactory |
|
{ |
|
static public ShaderInput GetShaderInput(BlackboardShaderInputOrder order) |
|
{ |
|
ShaderInput output; |
|
if (order.isKeyword) |
|
{ |
|
if (order.builtInKeyword == null) |
|
{ |
|
output = new ShaderKeyword(order.keywordType); |
|
} |
|
else |
|
{ |
|
output = order.builtInKeyword; |
|
} |
|
} |
|
else if (order.isDropdown) |
|
{ |
|
output = new ShaderDropdown(); |
|
} |
|
else |
|
{ |
|
switch (order.deprecatedPropertyName) |
|
{ |
|
case "Color": |
|
output = new ColorShaderProperty(order.version); |
|
break; |
|
default: |
|
output = null; |
|
AssertHelpers.Fail("BlackboardShaderInputFactory: Unknown deprecated property type."); |
|
break; |
|
} |
|
} |
|
|
|
return output; |
|
} |
|
} |
|
class AddShaderInputAction : IGraphDataAction |
|
{ |
|
public enum AddActionSource |
|
{ |
|
Default, |
|
AddMenu |
|
} |
|
|
|
void AddShaderInput(GraphData graphData) |
|
{ |
|
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out AddShaderInputAction"); |
|
|
|
// If type property is valid, create instance of that type |
|
if (blackboardItemType != null && blackboardItemType.IsSubclassOf(typeof(BlackboardItem))) |
|
{ |
|
shaderInputReference = (BlackboardItem)Activator.CreateInstance(blackboardItemType, true); |
|
} |
|
else if (m_ShaderInputReferenceGetter != null) |
|
{ |
|
shaderInputReference = m_ShaderInputReferenceGetter(); |
|
} |
|
// If type is null a direct override object must have been provided or else we are in an error-state |
|
else if (shaderInputReference == null) |
|
{ |
|
AssertHelpers.Fail("BlackboardController: Unable to complete Add Shader Input action."); |
|
return; |
|
} |
|
|
|
shaderInputReference.generatePropertyBlock = shaderInputReference.isExposable; |
|
|
|
if (graphData.owner != null) |
|
graphData.owner.RegisterCompleteObjectUndo("Add Shader Input"); |
|
else |
|
AssertHelpers.Fail("GraphObject is null while carrying out AddShaderInputAction"); |
|
|
|
graphData.AddGraphInput(shaderInputReference); |
|
|
|
// If no categoryToAddItemToGuid is provided, add the input to the default category |
|
if (categoryToAddItemToGuid == String.Empty) |
|
{ |
|
var defaultCategory = graphData.categories.FirstOrDefault(); |
|
AssertHelpers.IsNotNull(defaultCategory, "Default category reference is null."); |
|
if (defaultCategory != null) |
|
{ |
|
var addItemToCategoryAction = new AddItemToCategoryAction(); |
|
addItemToCategoryAction.categoryGuid = defaultCategory.categoryGuid; |
|
addItemToCategoryAction.itemToAdd = shaderInputReference; |
|
graphData.owner.graphDataStore.Dispatch(addItemToCategoryAction); |
|
} |
|
} |
|
else |
|
{ |
|
var addItemToCategoryAction = new AddItemToCategoryAction(); |
|
addItemToCategoryAction.categoryGuid = categoryToAddItemToGuid; |
|
addItemToCategoryAction.itemToAdd = shaderInputReference; |
|
graphData.owner.graphDataStore.Dispatch(addItemToCategoryAction); |
|
} |
|
} |
|
|
|
public static AddShaderInputAction AddDeprecatedPropertyAction(BlackboardShaderInputOrder order) |
|
{ |
|
return new() { shaderInputReference = BlackboardShaderInputFactory.GetShaderInput(order), addInputActionType = AddShaderInputAction.AddActionSource.AddMenu }; |
|
} |
|
|
|
public static AddShaderInputAction AddDropdownAction(BlackboardShaderInputOrder order) |
|
{ |
|
return new() { shaderInputReference = BlackboardShaderInputFactory.GetShaderInput(order), addInputActionType = AddShaderInputAction.AddActionSource.AddMenu }; |
|
} |
|
|
|
public static AddShaderInputAction AddKeywordAction(BlackboardShaderInputOrder order) |
|
{ |
|
return new() { shaderInputReference = BlackboardShaderInputFactory.GetShaderInput(order), addInputActionType = AddShaderInputAction.AddActionSource.AddMenu }; |
|
} |
|
|
|
public static AddShaderInputAction AddPropertyAction(Type shaderInputType) |
|
{ |
|
return new() { blackboardItemType = shaderInputType, addInputActionType = AddShaderInputAction.AddActionSource.AddMenu }; |
|
} |
|
|
|
public Action<GraphData> modifyGraphDataAction => AddShaderInput; |
|
// If this is a subclass of ShaderInput and is not null, then an object of this type is created to add to blackboard |
|
// If the type field above is null and this is provided, then it is directly used as the item to add to blackboard |
|
public BlackboardItem shaderInputReference { get; set; } |
|
public AddActionSource addInputActionType { get; set; } |
|
public string categoryToAddItemToGuid { get; set; } = String.Empty; |
|
|
|
Type blackboardItemType { get; set; } |
|
|
|
Func<BlackboardItem> m_ShaderInputReferenceGetter = null; |
|
} |
|
|
|
class ChangeGraphPathAction : IGraphDataAction |
|
{ |
|
void ChangeGraphPath(GraphData graphData) |
|
{ |
|
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ChangeGraphPathAction"); |
|
graphData.path = NewGraphPath; |
|
} |
|
|
|
public Action<GraphData> modifyGraphDataAction => ChangeGraphPath; |
|
|
|
public string NewGraphPath { get; set; } |
|
} |
|
|
|
class CopyShaderInputAction : IGraphDataAction |
|
{ |
|
void CopyShaderInput(GraphData graphData) |
|
{ |
|
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out CopyShaderInputAction"); |
|
AssertHelpers.IsNotNull(shaderInputToCopy, "ShaderInputToCopy is null while carrying out CopyShaderInputAction"); |
|
|
|
// Don't handle undo here as there are different contexts in which this action is used, that define the undo action |
|
// TODO: Perhaps a sign that each of those need to be made their own actions instead of conflating intent into a single action |
|
|
|
switch (shaderInputToCopy) |
|
{ |
|
case AbstractShaderProperty property: |
|
insertIndex = Mathf.Clamp(insertIndex, -1, graphData.properties.Count() - 1); |
|
|
|
var copiedProperty = (AbstractShaderProperty)graphData.AddCopyOfShaderInput(property, insertIndex); |
|
if (copiedProperty != null) // some property types cannot be duplicated (unknown types) |
|
{ |
|
// Update the property nodes that depends on the copied node |
|
foreach (var node in dependentNodeList) |
|
{ |
|
if (node is PropertyNode propertyNode) |
|
{ |
|
propertyNode.owner = graphData; |
|
propertyNode.property = copiedProperty; |
|
} |
|
} |
|
} |
|
|
|
|
|
copiedShaderInput = copiedProperty; |
|
break; |
|
|
|
case ShaderKeyword shaderKeyword: |
|
// InsertIndex gets passed in relative to the blackboard position of an item overall, |
|
// and not relative to the array sizes of the properties/keywords/dropdowns |
|
var keywordInsertIndex = insertIndex - graphData.properties.Count(); |
|
keywordInsertIndex = Mathf.Clamp(keywordInsertIndex, -1, graphData.keywords.Count() - 1); |
|
|
|
// Don't duplicate built-in keywords within the same graph |
|
if (shaderKeyword.isBuiltIn && graphData.keywords.Any(p => p.referenceName == shaderInputToCopy.referenceName)) |
|
return; |
|
|
|
var copiedKeyword = (ShaderKeyword)graphData.AddCopyOfShaderInput(shaderKeyword, keywordInsertIndex); |
|
|
|
// Update the keyword nodes that depends on the copied node |
|
foreach (var node in dependentNodeList) |
|
{ |
|
if (node is KeywordNode propertyNode) |
|
{ |
|
propertyNode.owner = graphData; |
|
propertyNode.keyword = copiedKeyword; |
|
} |
|
} |
|
|
|
copiedShaderInput = copiedKeyword; |
|
break; |
|
|
|
case ShaderDropdown shaderDropdown: |
|
// InsertIndex gets passed in relative to the blackboard position of an item overall, |
|
// and not relative to the array sizes of the properties/keywords/dropdowns |
|
var dropdownInsertIndex = insertIndex - graphData.properties.Count() - graphData.keywords.Count(); |
|
dropdownInsertIndex = Mathf.Clamp(dropdownInsertIndex, -1, graphData.dropdowns.Count() - 1); |
|
|
|
var copiedDropdown = (ShaderDropdown)graphData.AddCopyOfShaderInput(shaderDropdown, dropdownInsertIndex); |
|
|
|
// Update the dropdown nodes that depends on the copied node |
|
foreach (var node in dependentNodeList) |
|
{ |
|
if (node is DropdownNode propertyNode) |
|
{ |
|
propertyNode.owner = graphData; |
|
propertyNode.dropdown = copiedDropdown; |
|
} |
|
} |
|
|
|
copiedShaderInput = copiedDropdown; |
|
break; |
|
|
|
default: |
|
throw new ArgumentOutOfRangeException(); |
|
} |
|
|
|
if (copiedShaderInput != null) |
|
{ |
|
// If specific category to copy to is provided, find and use it |
|
foreach (var category in graphData.categories) |
|
{ |
|
if (category.categoryGuid == containingCategoryGuid) |
|
{ |
|
// Ensures that the new item gets added after the item it was duplicated from |
|
insertIndex += 1; |
|
// If the source item was already the last item in list, just add to end of list |
|
if (insertIndex >= category.childCount) |
|
insertIndex = -1; |
|
graphData.InsertItemIntoCategory(category.objectId, copiedShaderInput, insertIndex); |
|
return; |
|
} |
|
} |
|
|
|
// Else, add to default category |
|
graphData.categories.First().InsertItemIntoCategory(copiedShaderInput); |
|
} |
|
} |
|
|
|
public Action<GraphData> modifyGraphDataAction => CopyShaderInput; |
|
|
|
public IEnumerable<AbstractMaterialNode> dependentNodeList { get; set; } = new List<AbstractMaterialNode>(); |
|
|
|
public BlackboardItem shaderInputToCopy { get; set; } |
|
|
|
public BlackboardItem copiedShaderInput { get; set; } |
|
|
|
public string containingCategoryGuid { get; set; } |
|
|
|
public int insertIndex { get; set; } = -1; |
|
} |
|
|
|
class AddCategoryAction : IGraphDataAction |
|
{ |
|
void AddCategory(GraphData graphData) |
|
{ |
|
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out AddCategoryAction"); |
|
graphData.owner.RegisterCompleteObjectUndo("Add Category"); |
|
// If categoryDataReference is not null, directly add it to graphData |
|
if (categoryDataReference == null) |
|
categoryDataReference = new CategoryData(categoryName, childObjects); |
|
graphData.AddCategory(categoryDataReference); |
|
} |
|
|
|
public Action<GraphData> modifyGraphDataAction => AddCategory; |
|
|
|
// Direct reference to the categoryData to use if it is specified |
|
public CategoryData categoryDataReference { get; set; } |
|
public string categoryName { get; set; } = String.Empty; |
|
public List<ShaderInput> childObjects { get; set; } |
|
} |
|
|
|
class MoveCategoryAction : IGraphDataAction |
|
{ |
|
void MoveCategory(GraphData graphData) |
|
{ |
|
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out MoveCategoryAction"); |
|
graphData.owner.RegisterCompleteObjectUndo("Move Category"); |
|
// Handling for out of range moves is slightly different, but otherwise we need to reverse for insertion order. |
|
var guids = newIndexValue >= graphData.categories.Count() ? categoryGuids : categoryGuids.Reverse<string>(); |
|
foreach (var guid in categoryGuids) |
|
{ |
|
var cat = graphData.categories.FirstOrDefault(c => c.categoryGuid == guid); |
|
graphData.MoveCategory(cat, newIndexValue); |
|
} |
|
} |
|
|
|
public Action<GraphData> modifyGraphDataAction => MoveCategory; |
|
|
|
// Reference to the shader input being modified |
|
internal List<string> categoryGuids { get; set; } |
|
|
|
internal int newIndexValue { get; set; } |
|
} |
|
|
|
class AddItemToCategoryAction : IGraphDataAction |
|
{ |
|
public enum AddActionSource |
|
{ |
|
Default, |
|
DragDrop |
|
} |
|
|
|
void AddItemsToCategory(GraphData graphData) |
|
{ |
|
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out AddItemToCategoryAction"); |
|
graphData.owner.RegisterCompleteObjectUndo("Add Item to Category"); |
|
graphData.InsertItemIntoCategory(categoryGuid, itemToAdd, indexToAddItemAt); |
|
} |
|
|
|
public Action<GraphData> modifyGraphDataAction => AddItemsToCategory; |
|
|
|
public string categoryGuid { get; set; } |
|
|
|
public ShaderInput itemToAdd { get; set; } |
|
|
|
// By default an item is always added to the end of a category, if this value is set to something other than -1, will insert the item at that position within the category |
|
public int indexToAddItemAt { get; set; } = -1; |
|
|
|
public AddActionSource addActionSource { get; set; } |
|
} |
|
|
|
class CopyCategoryAction : IGraphDataAction |
|
{ |
|
void CopyCategory(GraphData graphData) |
|
{ |
|
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out CopyCategoryAction"); |
|
AssertHelpers.IsNotNull(categoryToCopyReference, "CategoryToCopyReference is null while carrying out CopyCategoryAction"); |
|
|
|
// This is called by MaterialGraphView currently, no need to repeat it here, though ideally it would live here |
|
//graphData.owner.RegisterCompleteObjectUndo("Copy Category"); |
|
|
|
newCategoryDataReference = graphData.CopyCategory(categoryToCopyReference); |
|
} |
|
|
|
// Reference to the new category created as a copy |
|
public CategoryData newCategoryDataReference { get; set; } |
|
|
|
// After category has been copied, store reference to it |
|
public CategoryData categoryToCopyReference { get; set; } |
|
|
|
public Action<GraphData> modifyGraphDataAction => CopyCategory; |
|
} |
|
|
|
class ShaderVariantLimitAction : IGraphDataAction |
|
{ |
|
public int currentVariantCount { get; set; } = 0; |
|
public int maxVariantCount { get; set; } = 0; |
|
|
|
public ShaderVariantLimitAction(int currentVariantCount, int maxVariantCount) |
|
{ |
|
this.maxVariantCount = maxVariantCount; |
|
this.currentVariantCount = currentVariantCount; |
|
} |
|
|
|
// There's no action actually performed on the graph, but we need to implement this as a valid function |
|
public Action<GraphData> modifyGraphDataAction => Empty; |
|
|
|
void Empty(GraphData graphData) |
|
{ |
|
} |
|
} |
|
|
|
class BlackboardController : SGViewController<GraphData, BlackboardViewModel> |
|
{ |
|
// Type changes (adds/removes of Types) only happen after a full assembly reload so its safe to make this static |
|
static IList<Type> s_ShaderInputTypes; |
|
|
|
static BlackboardController() |
|
{ |
|
var shaderInputTypes = TypeCache.GetTypesWithAttribute<BlackboardInputInfo>().ToList(); |
|
// Sort the ShaderInput by priority using the BlackboardInputInfo attribute |
|
shaderInputTypes.Sort((s1, s2) => |
|
{ |
|
var info1 = Attribute.GetCustomAttribute(s1, typeof(BlackboardInputInfo)) as BlackboardInputInfo; |
|
var info2 = Attribute.GetCustomAttribute(s2, typeof(BlackboardInputInfo)) as BlackboardInputInfo; |
|
|
|
if (info1.priority == info2.priority) |
|
return (info1.name ?? s1.Name).CompareTo(info2.name ?? s2.Name); |
|
else |
|
return info1.priority.CompareTo(info2.priority); |
|
}); |
|
|
|
s_ShaderInputTypes = shaderInputTypes.ToList(); |
|
} |
|
|
|
BlackboardCategoryController m_DefaultCategoryController = null; |
|
Dictionary<string, BlackboardCategoryController> m_BlackboardCategoryControllers = new Dictionary<string, BlackboardCategoryController>(); |
|
|
|
SGBlackboard m_Blackboard; |
|
|
|
internal SGBlackboard blackboard |
|
{ |
|
get => m_Blackboard; |
|
private set => m_Blackboard = value; |
|
} |
|
public string GetFirstSelectedCategoryGuid() |
|
{ |
|
if (m_Blackboard == null) |
|
{ |
|
return string.Empty; |
|
} |
|
var copiedSelectionList = new List<ISelectable>(m_Blackboard.selection); |
|
var selectedCategories = new List<SGBlackboardCategory>(); |
|
var selectedCategoryGuid = String.Empty; |
|
for (int i = 0; i < copiedSelectionList.Count; i++) |
|
{ |
|
var selectable = copiedSelectionList[i]; |
|
if (selectable is SGBlackboardCategory category) |
|
{ |
|
selectedCategories.Add(selectable as SGBlackboardCategory); |
|
} |
|
} |
|
if (selectedCategories.Any()) |
|
{ |
|
selectedCategoryGuid = selectedCategories[0].viewModel.associatedCategoryGuid; |
|
} |
|
return selectedCategoryGuid; |
|
} |
|
|
|
void InitializeViewModel(bool useDropdowns) |
|
{ |
|
// Clear the view model |
|
ViewModel.ResetViewModelData(); |
|
ViewModel.subtitle = BlackboardUtils.FormatPath(Model.path); |
|
BlackboardShaderInputOrder propertyTypesOrder = new BlackboardShaderInputOrder(); |
|
|
|
// Property data first |
|
foreach (var shaderInputType in s_ShaderInputTypes) |
|
{ |
|
if (shaderInputType.IsAbstract) |
|
continue; |
|
|
|
var info = Attribute.GetCustomAttribute(shaderInputType, typeof(BlackboardInputInfo)) as BlackboardInputInfo; |
|
string name = info?.name ?? ObjectNames.NicifyVariableName(shaderInputType.Name.Replace("ShaderProperty", "")); |
|
|
|
// QUICK FIX TO DEAL WITH DEPRECATED COLOR PROPERTY |
|
if (name.Equals("Color", StringComparison.InvariantCultureIgnoreCase) && ShaderGraphPreferences.allowDeprecatedBehaviors) |
|
{ |
|
propertyTypesOrder.isKeyword = false; |
|
propertyTypesOrder.deprecatedPropertyName = name; |
|
propertyTypesOrder.version = ColorShaderProperty.deprecatedVersion; |
|
ViewModel.propertyNameToAddActionMap.Add("Color (Deprecated)", AddShaderInputAction.AddDeprecatedPropertyAction(propertyTypesOrder)); |
|
ViewModel.propertyNameToAddActionMap.Add(name, AddShaderInputAction.AddPropertyAction(shaderInputType)); |
|
} |
|
else |
|
ViewModel.propertyNameToAddActionMap.Add(name, AddShaderInputAction.AddPropertyAction(shaderInputType)); |
|
} |
|
|
|
// Default Keywords next |
|
BlackboardShaderInputOrder keywordTypesOrder = new BlackboardShaderInputOrder(); |
|
keywordTypesOrder.isKeyword = true; |
|
keywordTypesOrder.keywordType = KeywordType.Boolean; |
|
ViewModel.defaultKeywordNameToAddActionMap.Add("Boolean", AddShaderInputAction.AddKeywordAction(keywordTypesOrder)); |
|
keywordTypesOrder.keywordType = KeywordType.Enum; |
|
ViewModel.defaultKeywordNameToAddActionMap.Add("Enum", AddShaderInputAction.AddKeywordAction(keywordTypesOrder)); |
|
|
|
// Built-In Keywords after that |
|
foreach (var builtinKeywordDescriptor in KeywordUtil.GetBuiltinKeywordDescriptors()) |
|
{ |
|
var keyword = ShaderKeyword.CreateBuiltInKeyword(builtinKeywordDescriptor); |
|
// Do not allow user to add built-in keywords that conflict with user-made keywords that have the same reference name or display name |
|
if (Model.keywords.Any(x => x.referenceName == keyword.referenceName || x.displayName == keyword.displayName)) |
|
{ |
|
ViewModel.disabledKeywordNameList.Add(keyword.displayName); |
|
} |
|
else |
|
{ |
|
keywordTypesOrder.builtInKeyword = (ShaderKeyword)keyword.Copy(); |
|
ViewModel.builtInKeywordNameToAddActionMap.Add(keyword.displayName, AddShaderInputAction.AddKeywordAction(keywordTypesOrder)); |
|
} |
|
} |
|
|
|
if (useDropdowns) |
|
{ |
|
BlackboardShaderInputOrder dropdownsOrder = new BlackboardShaderInputOrder(); |
|
dropdownsOrder.isDropdown = true; |
|
ViewModel.defaultDropdownNameToAdd = new Tuple<string, IGraphDataAction>("Dropdown", AddShaderInputAction.AddDropdownAction(dropdownsOrder)); |
|
} |
|
|
|
// Category data last |
|
var defaultNewCategoryReference = new CategoryData("Category"); |
|
ViewModel.addCategoryAction = new AddCategoryAction() { categoryDataReference = defaultNewCategoryReference }; |
|
|
|
ViewModel.requestModelChangeAction = this.RequestModelChange; |
|
ViewModel.categoryInfoList.AddRange(DataStore.State.categories.ToList()); |
|
} |
|
|
|
internal BlackboardController(GraphData model, BlackboardViewModel inViewModel, GraphDataStore graphDataStore) |
|
: base(model, inViewModel, graphDataStore) |
|
{ |
|
// TODO: hide this more generically for category types. |
|
bool useDropdowns = model.isSubGraph; |
|
InitializeViewModel(useDropdowns); |
|
|
|
blackboard = new SGBlackboard(ViewModel, this); |
|
|
|
// Add default category at the top of the blackboard (create it if it doesn't exist already) |
|
var existingDefaultCategory = DataStore.State.categories.FirstOrDefault(); |
|
if (existingDefaultCategory != null && existingDefaultCategory.IsNamedCategory() == false) |
|
{ |
|
AddBlackboardCategory(graphDataStore, existingDefaultCategory); |
|
} |
|
else |
|
{ |
|
// Any properties that don't already have a category (for example, if this graph is being loaded from an older version that doesn't have category data) |
|
var uncategorizedBlackboardItems = new List<ShaderInput>(); |
|
foreach (var shaderProperty in DataStore.State.properties) |
|
if (IsInputUncategorized(shaderProperty)) |
|
uncategorizedBlackboardItems.Add(shaderProperty); |
|
|
|
foreach (var shaderKeyword in DataStore.State.keywords) |
|
if (IsInputUncategorized(shaderKeyword)) |
|
uncategorizedBlackboardItems.Add(shaderKeyword); |
|
|
|
if (useDropdowns) |
|
{ |
|
foreach (var shaderDropdown in DataStore.State.dropdowns) |
|
if (IsInputUncategorized(shaderDropdown)) |
|
uncategorizedBlackboardItems.Add(shaderDropdown); |
|
} |
|
|
|
var addCategoryAction = new AddCategoryAction(); |
|
addCategoryAction.categoryDataReference = CategoryData.DefaultCategory(uncategorizedBlackboardItems); |
|
graphDataStore.Dispatch(addCategoryAction); |
|
} |
|
|
|
// Get the reference to default category controller after its been added |
|
m_DefaultCategoryController = m_BlackboardCategoryControllers.Values.FirstOrDefault(); |
|
AssertHelpers.IsNotNull(m_DefaultCategoryController, "Failed to instantiate default category."); |
|
|
|
// Handle loaded-in categories from graph first, skipping the first/default category |
|
foreach (var categoryData in ViewModel.categoryInfoList.Skip(1)) |
|
{ |
|
AddBlackboardCategory(graphDataStore, categoryData); |
|
} |
|
} |
|
|
|
internal string editorPrefsBaseKey => "unity.shadergraph." + DataStore.State.objectId; |
|
|
|
BlackboardCategoryController AddBlackboardCategory(GraphDataStore graphDataStore, CategoryData categoryInfo) |
|
{ |
|
var blackboardCategoryViewModel = new BlackboardCategoryViewModel(); |
|
blackboardCategoryViewModel.parentView = blackboard; |
|
blackboardCategoryViewModel.requestModelChangeAction = ViewModel.requestModelChangeAction; |
|
blackboardCategoryViewModel.name = categoryInfo.name; |
|
blackboardCategoryViewModel.associatedCategoryGuid = categoryInfo.categoryGuid; |
|
blackboardCategoryViewModel.isExpanded = EditorPrefs.GetBool($"{editorPrefsBaseKey}.{categoryInfo.categoryGuid}.{ChangeCategoryIsExpandedAction.kEditorPrefKey}", true); |
|
|
|
var blackboardCategoryController = new BlackboardCategoryController(categoryInfo, blackboardCategoryViewModel, graphDataStore); |
|
if (m_BlackboardCategoryControllers.ContainsKey(categoryInfo.categoryGuid) == false) |
|
{ |
|
m_BlackboardCategoryControllers.Add(categoryInfo.categoryGuid, blackboardCategoryController); |
|
} |
|
else |
|
{ |
|
AssertHelpers.Fail("Failed to add category controller due to category with same GUID already having been added."); |
|
return null; |
|
} |
|
return blackboardCategoryController; |
|
} |
|
|
|
// Creates controller, view and view model for a blackboard item and adds the view to the specified index in the category |
|
SGBlackboardRow InsertBlackboardRow(BlackboardItem shaderInput, int insertionIndex = -1) |
|
{ |
|
return m_DefaultCategoryController.InsertBlackboardRow(shaderInput, insertionIndex); |
|
} |
|
|
|
public void UpdateBlackboardTitle(string newTitle) |
|
{ |
|
ViewModel.title = newTitle; |
|
blackboard.title = ViewModel.title; |
|
} |
|
|
|
protected override void RequestModelChange(IGraphDataAction changeAction) |
|
{ |
|
DataStore.Dispatch(changeAction); |
|
} |
|
|
|
// Called by GraphDataStore.Subscribe after the model has been changed |
|
protected override void ModelChanged(GraphData graphData, IGraphDataAction changeAction) |
|
{ |
|
// Reconstruct view-model first |
|
// TODO: hide this more generically for category types. |
|
bool useDropdowns = graphData.isSubGraph; |
|
InitializeViewModel(useDropdowns); |
|
|
|
var graphView = ViewModel.parentView as MaterialGraphView; |
|
|
|
switch (changeAction) |
|
{ |
|
// If newly added input doesn't belong to any of the user-made categories, add it to the default category at top of blackboard |
|
case AddShaderInputAction addBlackboardItemAction: |
|
if (IsInputUncategorized(addBlackboardItemAction.shaderInputReference)) |
|
{ |
|
var blackboardRow = InsertBlackboardRow(addBlackboardItemAction.shaderInputReference); |
|
if (blackboardRow != null) |
|
{ |
|
var propertyView = blackboardRow.Q<SGBlackboardField>(); |
|
if (addBlackboardItemAction.addInputActionType == AddShaderInputAction.AddActionSource.AddMenu) |
|
propertyView.OpenTextEditor(); |
|
} |
|
} |
|
break; |
|
// Need to handle deletion of shader inputs here as opposed to BlackboardCategoryController, as currently, |
|
// once removed from the categories there is no way to associate an input with the category that owns it |
|
case DeleteShaderInputAction deleteShaderInputAction: |
|
foreach (var shaderInput in deleteShaderInputAction.shaderInputsToDelete) |
|
RemoveInputFromBlackboard(shaderInput); |
|
break; |
|
|
|
case HandleUndoRedoAction handleUndoRedoAction: |
|
ClearBlackboardCategories(); |
|
|
|
foreach (var categoryData in graphData.addedCategories) |
|
AddBlackboardCategory(DataStore, categoryData); |
|
|
|
m_DefaultCategoryController = m_BlackboardCategoryControllers.Values.FirstOrDefault(); |
|
|
|
break; |
|
case CopyShaderInputAction copyShaderInputAction: |
|
// In the specific case of only-one keywords like Material Quality and Raytracing, they can get copied, but because only one can exist, the output copied value is null |
|
if (copyShaderInputAction.copiedShaderInput != null && IsInputUncategorized(copyShaderInputAction.copiedShaderInput)) |
|
{ |
|
var blackboardRow = InsertBlackboardRow(copyShaderInputAction.copiedShaderInput, copyShaderInputAction.insertIndex); |
|
var propertyView = blackboardRow.Q<SGBlackboardField>(); |
|
graphView?.AddToSelectionNoUndoRecord(propertyView); |
|
} |
|
|
|
break; |
|
|
|
case AddCategoryAction addCategoryAction: |
|
AddBlackboardCategory(DataStore, addCategoryAction.categoryDataReference); |
|
// Iterate through anything that is selected currently |
|
foreach (var selectedElement in blackboard.selection.ToList()) |
|
{ |
|
if (selectedElement is SGBlackboardField { userData: ShaderInput shaderInput }) |
|
{ |
|
// If a blackboard item is selected, first remove it from the blackboard |
|
RemoveInputFromBlackboard(shaderInput); |
|
|
|
// Then add input to the new category |
|
var addItemToCategoryAction = new AddItemToCategoryAction(); |
|
addItemToCategoryAction.categoryGuid = addCategoryAction.categoryDataReference.categoryGuid; |
|
addItemToCategoryAction.itemToAdd = shaderInput; |
|
DataStore.Dispatch(addItemToCategoryAction); |
|
} |
|
} |
|
break; |
|
|
|
case DeleteCategoryAction deleteCategoryAction: |
|
// Clean up deleted categories |
|
foreach (var categoryGUID in deleteCategoryAction.categoriesToRemoveGuids) |
|
{ |
|
RemoveBlackboardCategory(categoryGUID); |
|
} |
|
break; |
|
|
|
case MoveCategoryAction moveCategoryAction: |
|
ClearBlackboardCategories(); |
|
foreach (var categoryData in ViewModel.categoryInfoList) |
|
AddBlackboardCategory(graphData.owner.graphDataStore, categoryData); |
|
break; |
|
|
|
case CopyCategoryAction copyCategoryAction: |
|
var blackboardCategory = AddBlackboardCategory(graphData.owner.graphDataStore, copyCategoryAction.newCategoryDataReference); |
|
if (blackboardCategory != null) |
|
graphView?.AddToSelectionNoUndoRecord(blackboardCategory.blackboardCategoryView); |
|
break; |
|
case ShaderVariantLimitAction shaderVariantLimitAction: |
|
blackboard.SetCurrentVariantUsage(shaderVariantLimitAction.currentVariantCount, shaderVariantLimitAction.maxVariantCount); |
|
break; |
|
} |
|
|
|
// Lets all event handlers this controller owns/manages know that the model has changed |
|
// Usually this is to update views and make them reconstruct themself from updated view-model |
|
//NotifyChange(changeAction); |
|
|
|
// Let child controllers know about changes to this controller so they may update themselves in turn |
|
//ApplyChanges(); |
|
} |
|
|
|
void RemoveInputFromBlackboard(ShaderInput shaderInput) |
|
{ |
|
// Check if input is in one of the categories |
|
foreach (var controller in m_BlackboardCategoryControllers.Values) |
|
{ |
|
var blackboardRow = controller.FindBlackboardRow(shaderInput); |
|
if (blackboardRow != null) |
|
{ |
|
controller.RemoveBlackboardRow(shaderInput); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
bool IsInputUncategorized(ShaderInput shaderInput) |
|
{ |
|
// Skip the first category controller as that is guaranteed to be the default category |
|
foreach (var categoryController in m_BlackboardCategoryControllers.Values.Skip(1)) |
|
{ |
|
if (categoryController.IsInputInCategory(shaderInput)) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
public SGBlackboardCategory GetBlackboardCategory(string inputGuid) |
|
{ |
|
foreach (var categoryController in m_BlackboardCategoryControllers.Values) |
|
{ |
|
if (categoryController.Model.categoryGuid == inputGuid) |
|
return categoryController.blackboardCategoryView; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
public SGBlackboardRow GetBlackboardRow(ShaderInput blackboardItem) |
|
{ |
|
foreach (var categoryController in m_BlackboardCategoryControllers.Values) |
|
{ |
|
var blackboardRow = categoryController.FindBlackboardRow(blackboardItem); |
|
if (blackboardRow != null) |
|
return blackboardRow; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
int numberOfCategories => m_BlackboardCategoryControllers.Count; |
|
|
|
// Gets the index after the currently selected shader input for pasting properties into this graph |
|
internal int GetInsertionIndexForPaste() |
|
{ |
|
if (blackboard?.selection == null || blackboard.selection.Count == 0) |
|
{ |
|
return 0; |
|
} |
|
|
|
foreach (ISelectable selection in blackboard.selection) |
|
{ |
|
if (selection is SGBlackboardField blackboardPropertyView) |
|
{ |
|
SGBlackboardRow row = blackboardPropertyView.GetFirstAncestorOfType<SGBlackboardRow>(); |
|
SGBlackboardCategory category = blackboardPropertyView.GetFirstAncestorOfType<SGBlackboardCategory>(); |
|
if (row == null || category == null) |
|
continue; |
|
int blackboardFieldIndex = category.IndexOf(row); |
|
|
|
return blackboardFieldIndex; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
void RemoveBlackboardCategory(string categoryGUID) |
|
{ |
|
m_BlackboardCategoryControllers.TryGetValue(categoryGUID, out var blackboardCategoryController); |
|
if (blackboardCategoryController != null) |
|
{ |
|
blackboardCategoryController.Destroy(); |
|
m_BlackboardCategoryControllers.Remove(categoryGUID); |
|
} |
|
else |
|
AssertHelpers.Fail("Tried to remove a category that doesn't exist. "); |
|
} |
|
|
|
void ClearBlackboardCategories() |
|
{ |
|
foreach (var categoryController in m_BlackboardCategoryControllers.Values) |
|
{ |
|
categoryController.Destroy(); |
|
} |
|
m_BlackboardCategoryControllers.Clear(); |
|
} |
|
|
|
// Meant to be used by UI testing in order to clear blackboard state |
|
internal void ResetBlackboardState() |
|
{ |
|
ClearBlackboardCategories(); |
|
var addCategoryAction = new AddCategoryAction(); |
|
addCategoryAction.categoryDataReference = CategoryData.DefaultCategory(); |
|
DataStore.Dispatch(addCategoryAction); |
|
} |
|
} |
|
}
|
|
|