initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,299 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.Experimental.GraphView;
using UnityEditor.Graphing;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.UIElements;
using GraphDataStore = UnityEditor.ShaderGraph.DataStore<UnityEditor.ShaderGraph.GraphData>;
using BlackboardItem = UnityEditor.ShaderGraph.Internal.ShaderInput;
using BlackboardItemController = UnityEditor.ShaderGraph.Drawing.ShaderInputViewController;
namespace UnityEditor.ShaderGraph.Drawing
{
class MoveShaderInputAction : IGraphDataAction
{
void MoveShaderInput(GraphData graphData)
{
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out MoveShaderInputAction");
AssertHelpers.IsNotNull(shaderInputReference, "ShaderInputReference is null while carrying out MoveShaderInputAction");
graphData.owner.RegisterCompleteObjectUndo("Move Graph Input");
graphData.MoveItemInCategory(shaderInputReference, newIndexValue, associatedCategoryGuid);
}
public Action<GraphData> modifyGraphDataAction => MoveShaderInput;
internal string associatedCategoryGuid { get; set; }
// Reference to the shader input being modified
internal ShaderInput shaderInputReference { get; set; }
internal int newIndexValue { get; set; }
}
class DeleteCategoryAction : IGraphDataAction
{
void RemoveCategory(GraphData graphData)
{
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out DeleteCategoryAction");
AssertHelpers.IsNotNull(categoriesToRemoveGuids, "CategoryToRemove is null while carrying out DeleteCategoryAction");
// This is called by MaterialGraphView currently, no need to repeat it here, though ideally it would live here
//graphData.owner.RegisterCompleteObjectUndo("Delete Category");
foreach (var categoryGUID in categoriesToRemoveGuids)
{
graphData.RemoveCategory(categoryGUID);
}
}
public Action<GraphData> modifyGraphDataAction => RemoveCategory;
// Reference to the guid(s) of categories being deleted
public HashSet<string> categoriesToRemoveGuids { get; set; } = new HashSet<string>();
}
class ChangeCategoryIsExpandedAction : IGraphDataAction
{
internal const string kEditorPrefKey = ".isCategoryExpanded";
void ChangeIsExpanded(GraphData graphData)
{
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ChangeIsExpanded on Category");
foreach (var catid in categoryGuids)
{
var key = $"{editorPrefsBaseKey}.{catid}.{kEditorPrefKey}";
var currentValue = EditorPrefs.GetBool(key, true);
if (currentValue != isExpanded)
{
EditorPrefs.SetBool(key, isExpanded);
}
}
}
public string editorPrefsBaseKey;
public List<string> categoryGuids { get; set; }
public bool isExpanded { get; set; }
public Action<GraphData> modifyGraphDataAction => ChangeIsExpanded;
}
class ChangeCategoryNameAction : IGraphDataAction
{
void ChangeCategoryName(GraphData graphData)
{
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ChangeCategoryNameAction");
graphData.owner.RegisterCompleteObjectUndo("Change Category Name");
graphData.ChangeCategoryName(categoryGuid, newCategoryNameValue);
}
public Action<GraphData> modifyGraphDataAction => ChangeCategoryName;
// Guid of the category being modified
public string categoryGuid { get; set; }
internal string newCategoryNameValue { get; set; }
}
class BlackboardCategoryController : SGViewController<CategoryData, BlackboardCategoryViewModel>
{
internal SGBlackboardCategory blackboardCategoryView => m_BlackboardCategoryView;
SGBlackboardCategory m_BlackboardCategoryView;
Dictionary<string, BlackboardItemController> m_BlackboardItemControllers = new Dictionary<string, ShaderInputViewController>();
SGBlackboard blackboard { get; set; }
internal BlackboardCategoryController(CategoryData categoryData, BlackboardCategoryViewModel categoryViewModel, GraphDataStore dataStore)
: base(categoryData, categoryViewModel, dataStore)
{
m_BlackboardCategoryView = new SGBlackboardCategory(categoryViewModel, this);
blackboard = categoryViewModel.parentView as SGBlackboard;
if (blackboard == null)
return;
blackboard.Add(m_BlackboardCategoryView);
// These make sure that the drag indicators are disabled whenever a drag action is cancelled without completing a drop
blackboard.RegisterCallback<MouseUpEvent>(evt =>
{
m_BlackboardCategoryView.OnDragActionCanceled();
});
blackboard.hideDragIndicatorAction += m_BlackboardCategoryView.OnDragActionCanceled;
foreach (var categoryItem in categoryData.Children)
{
if (categoryItem == null)
{
AssertHelpers.Fail("Failed to insert blackboard row into category due to shader input being null.");
continue;
}
InsertBlackboardRow(categoryItem);
}
}
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)
{
// If categoryData associated with this controller is removed by an operation, destroy controller and views associated
if (graphData.ContainsCategory(Model) == false)
{
this.Destroy();
return;
}
switch (changeAction)
{
case AddShaderInputAction addBlackboardItemAction:
if (addBlackboardItemAction.shaderInputReference != null && IsInputInCategory(addBlackboardItemAction.shaderInputReference))
{
var blackboardRow = FindBlackboardRow(addBlackboardItemAction.shaderInputReference);
if (blackboardRow == null)
blackboardRow = InsertBlackboardRow(addBlackboardItemAction.shaderInputReference);
// Rows should auto-expand when an input is first added
// blackboardRow.expanded = true;
var propertyView = blackboardRow.Q<SGBlackboardField>();
if (addBlackboardItemAction.addInputActionType == AddShaderInputAction.AddActionSource.AddMenu)
propertyView.OpenTextEditor();
}
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 && IsInputInCategory(copyShaderInputAction.copiedShaderInput))
{
var blackboardRow = InsertBlackboardRow(copyShaderInputAction.copiedShaderInput, copyShaderInputAction.insertIndex);
if (blackboardRow != null)
{
var graphView = ViewModel.parentView.GetFirstAncestorOfType<MaterialGraphView>();
var propertyView = blackboardRow.Q<SGBlackboardField>();
graphView?.AddToSelectionNoUndoRecord(propertyView);
}
}
break;
case AddItemToCategoryAction addItemToCategoryAction:
// If item was added to category that this controller manages, then add blackboard row to represent that item
if (addItemToCategoryAction.itemToAdd != null && addItemToCategoryAction.categoryGuid == ViewModel.associatedCategoryGuid)
{
InsertBlackboardRow(addItemToCategoryAction.itemToAdd, addItemToCategoryAction.indexToAddItemAt);
}
else
{
// If the added input has been added to a category other than this one, and it used to belong to this category,
// Then cleanup the controller and view that used to represent that input
foreach (var key in m_BlackboardItemControllers.Keys)
{
var blackboardItemController = m_BlackboardItemControllers[key];
if (blackboardItemController.Model == addItemToCategoryAction.itemToAdd)
{
RemoveBlackboardRow(addItemToCategoryAction.itemToAdd);
break;
}
}
}
break;
case DeleteCategoryAction deleteCategoryAction:
if (deleteCategoryAction.categoriesToRemoveGuids.Contains(ViewModel.associatedCategoryGuid))
{
this.Destroy();
return;
}
break;
case ChangeCategoryIsExpandedAction changeIsExpandedAction:
if (changeIsExpandedAction.categoryGuids.Contains(ViewModel.associatedCategoryGuid))
{
ViewModel.isExpanded = changeIsExpandedAction.isExpanded;
m_BlackboardCategoryView.TryDoFoldout(changeIsExpandedAction.isExpanded);
}
break;
case ChangeCategoryNameAction changeCategoryNameAction:
if (changeCategoryNameAction.categoryGuid == ViewModel.associatedCategoryGuid)
{
ViewModel.name = Model.name;
m_BlackboardCategoryView.title = ViewModel.name;
}
break;
}
}
internal bool IsInputInCategory(ShaderInput shaderInput)
{
return Model != null && Model.IsItemInCategory(shaderInput);
}
internal SGBlackboardRow FindBlackboardRow(ShaderInput shaderInput)
{
m_BlackboardItemControllers.TryGetValue(shaderInput.objectId, out var associatedController);
return associatedController?.BlackboardItemView;
}
// Creates controller, view and view model for a blackboard item and adds the view to the specified index in the category
// By default adds it to the end of the list if no insertionIndex specified
internal SGBlackboardRow InsertBlackboardRow(BlackboardItem shaderInput, int insertionIndex = -1)
{
var shaderInputViewModel = new ShaderInputViewModel()
{
model = shaderInput,
parentView = blackboardCategoryView,
};
var blackboardItemController = new BlackboardItemController(shaderInput, shaderInputViewModel, DataStore);
m_BlackboardItemControllers.TryGetValue(shaderInput.objectId, out var existingItemController);
if (existingItemController == null)
{
m_BlackboardItemControllers.Add(shaderInput.objectId, blackboardItemController);
// If no index specified, add to end of category
if (insertionIndex == -1)
blackboardCategoryView.Add(blackboardItemController.BlackboardItemView);
else
blackboardCategoryView.Insert(insertionIndex, blackboardItemController.BlackboardItemView);
blackboardCategoryView.MarkDirtyRepaint();
return blackboardItemController.BlackboardItemView;
}
else
{
AssertHelpers.Fail("Tried to add blackboard item that already exists to category.");
return null;
}
}
internal void RemoveBlackboardRow(BlackboardItem shaderInput)
{
m_BlackboardItemControllers.TryGetValue(shaderInput.objectId, out var associatedBlackboardItemController);
if (associatedBlackboardItemController != null)
{
associatedBlackboardItemController.Destroy();
m_BlackboardItemControllers.Remove(shaderInput.objectId);
}
else
AssertHelpers.Fail("Failed to find associated blackboard item controller for shader input that was just deleted. Cannot clean up view associated with input.");
}
void ClearBlackboardRows()
{
foreach (var shaderInputViewController in m_BlackboardItemControllers.Values)
shaderInputViewController.Destroy();
m_BlackboardItemControllers.Clear();
}
public override void Destroy()
{
Cleanup();
m_BlackboardCategoryView?.RemoveFromHierarchy();
ClearBlackboardRows();
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 415e66e3c4ba4ffd834a7d90349db989
timeCreated: 1612206426

View file

@ -0,0 +1,818 @@
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);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8b25321bfcc6454b9c673c127a84bb0c
timeCreated: 1608602455

View file

@ -0,0 +1,199 @@
using System;
using System.Collections;
using System.Linq;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.ShaderGraph.Drawing;
using GraphDataStore = UnityEditor.ShaderGraph.DataStore<UnityEditor.ShaderGraph.GraphData>;
namespace UnityEditor.ShaderGraph
{
class DummyChangeAction : IGraphDataAction
{
void OnDummyChangeAction(GraphData m_GraphData)
{
}
public Action<GraphData> modifyGraphDataAction => OnDummyChangeAction;
}
struct SGControllerChangedEvent
{
public ISGControlledElement target;
public SGController controller;
public IGraphDataAction change;
private bool m_PropagationStopped;
void StopPropagation()
{
m_PropagationStopped = true;
}
public bool isPropagationStopped => m_PropagationStopped;
}
class SGControllerEvent
{
ISGControlledElement target = null;
SGControllerEvent(ISGControlledElement controlledTarget)
{
target = controlledTarget;
}
}
abstract class SGController
{
public bool m_DisableCalled = false;
protected IGraphDataAction DummyChange = new DummyChangeAction();
public virtual void OnDisable()
{
if (m_DisableCalled)
Debug.LogError(GetType().Name + ".Disable called twice");
m_DisableCalled = true;
foreach (var element in allChildren)
{
UnityEngine.Profiling.Profiler.BeginSample(element.GetType().Name + ".OnDisable");
element.OnDisable();
UnityEngine.Profiling.Profiler.EndSample();
}
}
internal void RegisterHandler(ISGControlledElement handler)
{
//Debug.Log("RegisterHandler of " + handler.GetType().Name + " on " + GetType().Name );
if (m_EventHandlers.Contains(handler))
Debug.LogError("Handler registered twice");
else
{
m_EventHandlers.Add(handler);
NotifyEventHandler(handler, DummyChange);
}
}
internal void UnregisterHandler(ISGControlledElement handler)
{
m_EventHandlers.Remove(handler);
}
protected void NotifyChange(IGraphDataAction changeAction)
{
var eventHandlers = m_EventHandlers.ToArray(); // Some notification may trigger Register/Unregister so duplicate the collection.
foreach (var eventHandler in eventHandlers)
{
UnityEngine.Profiling.Profiler.BeginSample("NotifyChange:" + eventHandler.GetType().Name);
NotifyEventHandler(eventHandler, changeAction);
UnityEngine.Profiling.Profiler.EndSample();
}
}
void NotifyEventHandler(ISGControlledElement eventHandler, IGraphDataAction changeAction)
{
SGControllerChangedEvent e = new SGControllerChangedEvent();
e.controller = this;
e.target = eventHandler;
e.change = changeAction;
eventHandler.OnControllerChanged(ref e);
if (e.isPropagationStopped)
return;
if (eventHandler is VisualElement)
{
var element = eventHandler as VisualElement;
eventHandler = element.GetFirstOfType<ISGControlledElement>();
while (eventHandler != null)
{
eventHandler.OnControllerChanged(ref e);
if (e.isPropagationStopped)
break;
eventHandler = (eventHandler as VisualElement).GetFirstAncestorOfType<ISGControlledElement>();
}
}
}
public void SendEvent(SGControllerEvent e)
{
var eventHandlers = m_EventHandlers.ToArray(); // Some notification may trigger Register/Unregister so duplicate the collection.
foreach (var eventHandler in eventHandlers)
{
eventHandler.OnControllerEvent(e);
}
}
public abstract void ApplyChanges();
public virtual IEnumerable<SGController> allChildren
{
get { return Enumerable.Empty<SGController>(); }
}
List<ISGControlledElement> m_EventHandlers = new List<ISGControlledElement>();
}
abstract class SGController<T> : SGController
{
GraphDataStore m_DataStore;
protected GraphDataStore DataStore => m_DataStore;
protected SGController(T model, GraphDataStore dataStore)
{
m_Model = model;
m_DataStore = dataStore;
DataStore.Subscribe += ModelChanged;
}
protected abstract void RequestModelChange(IGraphDataAction changeAction);
protected abstract void ModelChanged(GraphData graphData, IGraphDataAction changeAction);
T m_Model;
public T Model => m_Model;
// Cleanup delegate association before destruction
public void Cleanup()
{
DataStore.Subscribe -= ModelChanged;
}
}
abstract class SGViewController<ModelType, ViewModelType> : SGController<ModelType>
{
protected SGViewController(ModelType model, ViewModelType viewModel, GraphDataStore graphDataStore) : base(model, graphDataStore)
{
m_ViewModel = viewModel;
try
{
// Need ViewModel to be initialized before we call ModelChanged() [as view model might need to update]
ModelChanged(DataStore.State, DummyChange);
}
catch (Exception e)
{
Debug.Log("Failed to initialize View Controller of type: " + this.GetType() + " due to exception: " + e);
}
}
// Holds data specific to the views this controller is responsible for
ViewModelType m_ViewModel;
public ViewModelType ViewModel => m_ViewModel;
public override void ApplyChanges()
{
foreach (var controller in allChildren)
{
controller.ApplyChanges();
}
}
public virtual void Destroy() { }
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1dae36c782bd4d8babb267707f7596f3
timeCreated: 1608256298

View file

@ -0,0 +1,355 @@
using UnityEngine;
using System;
using System.Collections.Generic;
using UnityEditor.Graphing;
using UnityEditor.ShaderGraph.Drawing.Controls;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine.Assertions;
using UnityEngine.UIElements;
using GraphDataStore = UnityEditor.ShaderGraph.DataStore<UnityEditor.ShaderGraph.GraphData>;
namespace UnityEditor.ShaderGraph.Drawing
{
class ChangeExposedFlagAction : IGraphDataAction
{
internal ChangeExposedFlagAction(ShaderInput shaderInput, bool newIsExposed)
{
this.shaderInputReference = shaderInput;
this.newIsExposedValue = newIsExposed;
this.oldIsExposedValue = shaderInput.generatePropertyBlock;
}
void ChangeExposedFlag(GraphData graphData)
{
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ChangeExposedFlagAction");
AssertHelpers.IsNotNull(shaderInputReference, "ShaderInputReference is null while carrying out ChangeExposedFlagAction");
// The Undos are currently handled in ShaderInputPropertyDrawer but we want to move that out from there and handle here
//graphData.owner.RegisterCompleteObjectUndo("Change Exposed Toggle");
shaderInputReference.generatePropertyBlock = newIsExposedValue;
}
public Action<GraphData> modifyGraphDataAction => ChangeExposedFlag;
// Reference to the shader input being modified
internal ShaderInput shaderInputReference { get; private set; }
// New value of whether the shader input should be exposed to the material inspector
internal bool newIsExposedValue { get; private set; }
internal bool oldIsExposedValue { get; private set; }
}
class ChangePropertyValueAction : IGraphDataAction
{
void ChangePropertyValue(GraphData graphData)
{
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ChangePropertyValueAction");
AssertHelpers.IsNotNull(shaderInputReference, "ShaderPropertyReference is null while carrying out ChangePropertyValueAction");
// The Undos are currently handled in ShaderInputPropertyDrawer but we want to move that out from there and handle here
//graphData.owner.RegisterCompleteObjectUndo("Change Property Value");
switch (shaderInputReference)
{
case BooleanShaderProperty booleanProperty:
booleanProperty.value = ((ToggleData)newShaderInputValue).isOn;
break;
case Vector1ShaderProperty vector1Property:
vector1Property.value = (float)newShaderInputValue;
break;
case Vector2ShaderProperty vector2Property:
vector2Property.value = (Vector2)newShaderInputValue;
break;
case Vector3ShaderProperty vector3Property:
vector3Property.value = (Vector3)newShaderInputValue;
break;
case Vector4ShaderProperty vector4Property:
vector4Property.value = (Vector4)newShaderInputValue;
break;
case ColorShaderProperty colorProperty:
colorProperty.value = (Color)newShaderInputValue;
break;
case Texture2DShaderProperty texture2DProperty:
texture2DProperty.value.texture = (Texture)newShaderInputValue;
break;
case Texture2DArrayShaderProperty texture2DArrayProperty:
texture2DArrayProperty.value.textureArray = (Texture2DArray)newShaderInputValue;
break;
case Texture3DShaderProperty texture3DProperty:
texture3DProperty.value.texture = (Texture3D)newShaderInputValue;
break;
case CubemapShaderProperty cubemapProperty:
cubemapProperty.value.cubemap = (Cubemap)newShaderInputValue;
break;
case Matrix2ShaderProperty matrix2Property:
matrix2Property.value = (Matrix4x4)newShaderInputValue;
break;
case Matrix3ShaderProperty matrix3Property:
matrix3Property.value = (Matrix4x4)newShaderInputValue;
break;
case Matrix4ShaderProperty matrix4Property:
matrix4Property.value = (Matrix4x4)newShaderInputValue;
break;
case SamplerStateShaderProperty samplerStateProperty:
samplerStateProperty.value = (TextureSamplerState)newShaderInputValue;
break;
case GradientShaderProperty gradientProperty:
gradientProperty.value = (Gradient)newShaderInputValue;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public Action<GraphData> modifyGraphDataAction => ChangePropertyValue;
// Reference to the shader input being modified
internal ShaderInput shaderInputReference { get; set; }
// New value of the shader property
internal object newShaderInputValue { get; set; }
}
class ChangeDisplayNameAction : IGraphDataAction
{
void ChangeDisplayName(GraphData graphData)
{
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ChangeDisplayNameAction");
AssertHelpers.IsNotNull(shaderInputReference, "ShaderInputReference is null while carrying out ChangeDisplayNameAction");
graphData.owner.RegisterCompleteObjectUndo("Change Display Name");
if (newDisplayNameValue != shaderInputReference.displayName)
{
shaderInputReference.SetDisplayNameAndSanitizeForGraph(graphData, newDisplayNameValue);
}
}
public Action<GraphData> modifyGraphDataAction => ChangeDisplayName;
// Reference to the shader input being modified
internal ShaderInput shaderInputReference { get; set; }
internal string newDisplayNameValue { get; set; }
}
class ChangeReferenceNameAction : IGraphDataAction
{
void ChangeReferenceName(GraphData graphData)
{
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ChangeReferenceNameAction");
AssertHelpers.IsNotNull(shaderInputReference, "ShaderInputReference is null while carrying out ChangeReferenceNameAction");
// The Undos are currently handled in ShaderInputPropertyDrawer but we want to move that out from there and handle here
//graphData.owner.RegisterCompleteObjectUndo("Change Reference Name");
if (newReferenceNameValue != shaderInputReference.overrideReferenceName)
{
graphData.SanitizeGraphInputReferenceName(shaderInputReference, newReferenceNameValue);
}
}
public Action<GraphData> modifyGraphDataAction => ChangeReferenceName;
// Reference to the shader input being modified
internal ShaderInput shaderInputReference { get; set; }
internal string newReferenceNameValue { get; set; }
}
class ResetReferenceNameAction : IGraphDataAction
{
void ResetReferenceName(GraphData graphData)
{
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ResetReferenceNameAction");
AssertHelpers.IsNotNull(shaderInputReference, "ShaderInputReference is null while carrying out ResetReferenceNameAction");
graphData.owner.RegisterCompleteObjectUndo("Reset Reference Name");
shaderInputReference.overrideReferenceName = null;
}
public Action<GraphData> modifyGraphDataAction => ResetReferenceName;
// Reference to the shader input being modified
internal ShaderInput shaderInputReference { get; set; }
}
class DeleteShaderInputAction : IGraphDataAction
{
void DeleteShaderInput(GraphData graphData)
{
AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out DeleteShaderInputAction");
AssertHelpers.IsNotNull(shaderInputsToDelete, "ShaderInputsToDelete is null while carrying out DeleteShaderInputAction");
// This is called by MaterialGraphView currently, no need to repeat it here, though ideally it would live here
//graphData.owner.RegisterCompleteObjectUndo("Delete Graph Input(s)");
foreach (var shaderInput in shaderInputsToDelete)
{
graphData.RemoveGraphInput(shaderInput);
}
}
public Action<GraphData> modifyGraphDataAction => DeleteShaderInput;
// Reference to the shader input(s) being deleted
internal IList<ShaderInput> shaderInputsToDelete { get; set; } = new List<ShaderInput>();
}
class ShaderInputViewController : SGViewController<ShaderInput, ShaderInputViewModel>
{
// Exposed for PropertyView
internal GraphData graphData => DataStore.State;
internal ShaderInputViewController(ShaderInput shaderInput, ShaderInputViewModel inViewModel, GraphDataStore graphDataStore)
: base(shaderInput, inViewModel, graphDataStore)
{
InitializeViewModel();
m_SgBlackboardField = new SGBlackboardField(ViewModel);
m_SgBlackboardField.controller = this;
m_BlackboardRowView = new SGBlackboardRow(m_SgBlackboardField, null);
m_BlackboardRowView.expanded = SessionState.GetBool($"Unity.ShaderGraph.Input.{shaderInput.objectId}.isExpanded", false);
}
void InitializeViewModel()
{
if (Model == null)
{
AssertHelpers.Fail("Could not initialize shader input view model as shader input was null.");
return;
}
ViewModel.model = Model;
ViewModel.isSubGraph = DataStore.State.isSubGraph;
ViewModel.isInputExposed = (DataStore.State.isSubGraph || (Model.isExposable && Model.generatePropertyBlock));
ViewModel.inputName = Model.displayName;
switch (Model)
{
case AbstractShaderProperty shaderProperty:
ViewModel.inputTypeName = shaderProperty.GetPropertyTypeString();
// Handles upgrade fix for deprecated old Color property
shaderProperty.onBeforeVersionChange += (_) => graphData.owner.RegisterCompleteObjectUndo($"Change {shaderProperty.displayName} Version");
break;
case ShaderKeyword shaderKeyword:
ViewModel.inputTypeName = shaderKeyword.keywordType + " Keyword";
ViewModel.inputTypeName = shaderKeyword.isBuiltIn ? "Built-in " + ViewModel.inputTypeName : ViewModel.inputTypeName;
break;
case ShaderDropdown shaderDropdown:
ViewModel.inputTypeName = "Dropdown";
break;
}
ViewModel.requestModelChangeAction = this.RequestModelChange;
}
SGBlackboardRow m_BlackboardRowView;
SGBlackboardField m_SgBlackboardField;
internal SGBlackboardRow BlackboardItemView => m_BlackboardRowView;
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)
{
switch (changeAction)
{
case ChangeExposedFlagAction changeExposedFlagAction:
// ModelChanged is called overzealously on everything
// but we only care if the action pertains to our Model
if (changeExposedFlagAction.shaderInputReference == Model)
{
ViewModel.isInputExposed = Model.generatePropertyBlock;
if (changeExposedFlagAction.oldIsExposedValue != changeExposedFlagAction.newIsExposedValue)
DirtyNodes(ModificationScope.Graph);
m_SgBlackboardField.UpdateFromViewModel();
}
break;
case ChangePropertyValueAction changePropertyValueAction:
if (changePropertyValueAction.shaderInputReference == Model)
{
DirtyNodes(ModificationScope.Graph);
m_SgBlackboardField.MarkDirtyRepaint();
}
break;
case ResetReferenceNameAction resetReferenceNameAction:
if (resetReferenceNameAction.shaderInputReference == Model)
{
DirtyNodes(ModificationScope.Graph);
}
break;
case ChangeReferenceNameAction changeReferenceNameAction:
if (changeReferenceNameAction.shaderInputReference == Model)
{
DirtyNodes(ModificationScope.Graph);
}
break;
case ChangeDisplayNameAction changeDisplayNameAction:
if (changeDisplayNameAction.shaderInputReference == Model)
{
ViewModel.inputName = Model.displayName;
DirtyNodes(ModificationScope.Topological);
m_SgBlackboardField.UpdateFromViewModel();
}
break;
}
}
// TODO: This should communicate to node controllers instead of searching for the nodes themselves everytime, but that's going to take a while...
internal void DirtyNodes(ModificationScope modificationScope = ModificationScope.Node)
{
switch (Model)
{
case AbstractShaderProperty property:
var graphEditorView = m_BlackboardRowView.GetFirstAncestorOfType<GraphEditorView>();
if (graphEditorView == null)
return;
var colorManager = graphEditorView.colorManager;
var nodes = graphEditorView.graphView.Query<MaterialNodeView>().ToList();
colorManager.SetNodesDirty(nodes);
colorManager.UpdateNodeViews(nodes);
foreach (var node in DataStore.State.GetNodes<PropertyNode>())
{
node.Dirty(modificationScope);
}
break;
case ShaderKeyword keyword:
foreach (var node in DataStore.State.GetNodes<KeywordNode>())
{
node.UpdateNode();
node.Dirty(modificationScope);
}
// Cant determine if Sub Graphs contain the keyword so just update them
foreach (var node in DataStore.State.GetNodes<SubGraphNode>())
{
node.Dirty(modificationScope);
}
break;
case ShaderDropdown dropdown:
foreach (var node in DataStore.State.GetNodes<DropdownNode>())
{
node.UpdateNode();
node.Dirty(modificationScope);
}
// Cant determine if Sub Graphs contain the dropdown so just update them
foreach (var node in DataStore.State.GetNodes<SubGraphNode>())
{
node.Dirty(modificationScope);
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override void Destroy()
{
Cleanup();
BlackboardItemView.RemoveFromHierarchy();
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8e48f61a99c146b1a223fe8ad61a6b55
timeCreated: 1611265477