Sin descripción
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.

BlackboardController.cs 37KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using UnityEngine;
  4. using UnityEditor.Experimental.GraphView;
  5. using UnityEngine.UIElements;
  6. using System;
  7. using UnityEditor.Graphing;
  8. using UnityEditor.ShaderGraph.Internal;
  9. using GraphDataStore = UnityEditor.ShaderGraph.DataStore<UnityEditor.ShaderGraph.GraphData>;
  10. using BlackboardItem = UnityEditor.ShaderGraph.Internal.ShaderInput;
  11. namespace UnityEditor.ShaderGraph.Drawing
  12. {
  13. struct BlackboardShaderInputOrder
  14. {
  15. public bool isKeyword;
  16. public bool isDropdown;
  17. public KeywordType keywordType;
  18. public ShaderKeyword builtInKeyword;
  19. public string deprecatedPropertyName;
  20. public int version;
  21. }
  22. class BlackboardShaderInputFactory
  23. {
  24. static public ShaderInput GetShaderInput(BlackboardShaderInputOrder order)
  25. {
  26. ShaderInput output;
  27. if (order.isKeyword)
  28. {
  29. if (order.builtInKeyword == null)
  30. {
  31. output = new ShaderKeyword(order.keywordType);
  32. }
  33. else
  34. {
  35. output = order.builtInKeyword;
  36. }
  37. }
  38. else if (order.isDropdown)
  39. {
  40. output = new ShaderDropdown();
  41. }
  42. else
  43. {
  44. switch (order.deprecatedPropertyName)
  45. {
  46. case "Color":
  47. output = new ColorShaderProperty(order.version);
  48. break;
  49. default:
  50. output = null;
  51. AssertHelpers.Fail("BlackboardShaderInputFactory: Unknown deprecated property type.");
  52. break;
  53. }
  54. }
  55. return output;
  56. }
  57. }
  58. class AddShaderInputAction : IGraphDataAction
  59. {
  60. public enum AddActionSource
  61. {
  62. Default,
  63. AddMenu
  64. }
  65. void AddShaderInput(GraphData graphData)
  66. {
  67. AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out AddShaderInputAction");
  68. // If type property is valid, create instance of that type
  69. if (blackboardItemType != null && blackboardItemType.IsSubclassOf(typeof(BlackboardItem)))
  70. {
  71. shaderInputReference = (BlackboardItem)Activator.CreateInstance(blackboardItemType, true);
  72. }
  73. else if (m_ShaderInputReferenceGetter != null)
  74. {
  75. shaderInputReference = m_ShaderInputReferenceGetter();
  76. }
  77. // If type is null a direct override object must have been provided or else we are in an error-state
  78. else if (shaderInputReference == null)
  79. {
  80. AssertHelpers.Fail("BlackboardController: Unable to complete Add Shader Input action.");
  81. return;
  82. }
  83. shaderInputReference.generatePropertyBlock = shaderInputReference.isExposable;
  84. if (graphData.owner != null)
  85. graphData.owner.RegisterCompleteObjectUndo("Add Shader Input");
  86. else
  87. AssertHelpers.Fail("GraphObject is null while carrying out AddShaderInputAction");
  88. graphData.AddGraphInput(shaderInputReference);
  89. // If no categoryToAddItemToGuid is provided, add the input to the default category
  90. if (categoryToAddItemToGuid == String.Empty)
  91. {
  92. var defaultCategory = graphData.categories.FirstOrDefault();
  93. AssertHelpers.IsNotNull(defaultCategory, "Default category reference is null.");
  94. if (defaultCategory != null)
  95. {
  96. var addItemToCategoryAction = new AddItemToCategoryAction();
  97. addItemToCategoryAction.categoryGuid = defaultCategory.categoryGuid;
  98. addItemToCategoryAction.itemToAdd = shaderInputReference;
  99. graphData.owner.graphDataStore.Dispatch(addItemToCategoryAction);
  100. }
  101. }
  102. else
  103. {
  104. var addItemToCategoryAction = new AddItemToCategoryAction();
  105. addItemToCategoryAction.categoryGuid = categoryToAddItemToGuid;
  106. addItemToCategoryAction.itemToAdd = shaderInputReference;
  107. graphData.owner.graphDataStore.Dispatch(addItemToCategoryAction);
  108. }
  109. }
  110. public static AddShaderInputAction AddDeprecatedPropertyAction(BlackboardShaderInputOrder order)
  111. {
  112. return new() { shaderInputReference = BlackboardShaderInputFactory.GetShaderInput(order), addInputActionType = AddShaderInputAction.AddActionSource.AddMenu };
  113. }
  114. public static AddShaderInputAction AddDropdownAction(BlackboardShaderInputOrder order)
  115. {
  116. return new() { shaderInputReference = BlackboardShaderInputFactory.GetShaderInput(order), addInputActionType = AddShaderInputAction.AddActionSource.AddMenu };
  117. }
  118. public static AddShaderInputAction AddKeywordAction(BlackboardShaderInputOrder order)
  119. {
  120. return new() { shaderInputReference = BlackboardShaderInputFactory.GetShaderInput(order), addInputActionType = AddShaderInputAction.AddActionSource.AddMenu };
  121. }
  122. public static AddShaderInputAction AddPropertyAction(Type shaderInputType)
  123. {
  124. return new() { blackboardItemType = shaderInputType, addInputActionType = AddShaderInputAction.AddActionSource.AddMenu };
  125. }
  126. public Action<GraphData> modifyGraphDataAction => AddShaderInput;
  127. // If this is a subclass of ShaderInput and is not null, then an object of this type is created to add to blackboard
  128. // If the type field above is null and this is provided, then it is directly used as the item to add to blackboard
  129. public BlackboardItem shaderInputReference { get; set; }
  130. public AddActionSource addInputActionType { get; set; }
  131. public string categoryToAddItemToGuid { get; set; } = String.Empty;
  132. Type blackboardItemType { get; set; }
  133. Func<BlackboardItem> m_ShaderInputReferenceGetter = null;
  134. }
  135. class ChangeGraphPathAction : IGraphDataAction
  136. {
  137. void ChangeGraphPath(GraphData graphData)
  138. {
  139. AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ChangeGraphPathAction");
  140. graphData.path = NewGraphPath;
  141. }
  142. public Action<GraphData> modifyGraphDataAction => ChangeGraphPath;
  143. public string NewGraphPath { get; set; }
  144. }
  145. class CopyShaderInputAction : IGraphDataAction
  146. {
  147. void CopyShaderInput(GraphData graphData)
  148. {
  149. AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out CopyShaderInputAction");
  150. AssertHelpers.IsNotNull(shaderInputToCopy, "ShaderInputToCopy is null while carrying out CopyShaderInputAction");
  151. // Don't handle undo here as there are different contexts in which this action is used, that define the undo action
  152. // TODO: Perhaps a sign that each of those need to be made their own actions instead of conflating intent into a single action
  153. switch (shaderInputToCopy)
  154. {
  155. case AbstractShaderProperty property:
  156. insertIndex = Mathf.Clamp(insertIndex, -1, graphData.properties.Count() - 1);
  157. var copiedProperty = (AbstractShaderProperty)graphData.AddCopyOfShaderInput(property, insertIndex);
  158. if (copiedProperty != null) // some property types cannot be duplicated (unknown types)
  159. {
  160. // Update the property nodes that depends on the copied node
  161. foreach (var node in dependentNodeList)
  162. {
  163. if (node is PropertyNode propertyNode)
  164. {
  165. propertyNode.owner = graphData;
  166. propertyNode.property = copiedProperty;
  167. }
  168. }
  169. }
  170. copiedShaderInput = copiedProperty;
  171. break;
  172. case ShaderKeyword shaderKeyword:
  173. // InsertIndex gets passed in relative to the blackboard position of an item overall,
  174. // and not relative to the array sizes of the properties/keywords/dropdowns
  175. var keywordInsertIndex = insertIndex - graphData.properties.Count();
  176. keywordInsertIndex = Mathf.Clamp(keywordInsertIndex, -1, graphData.keywords.Count() - 1);
  177. // Don't duplicate built-in keywords within the same graph
  178. if (shaderKeyword.isBuiltIn && graphData.keywords.Any(p => p.referenceName == shaderInputToCopy.referenceName))
  179. return;
  180. var copiedKeyword = (ShaderKeyword)graphData.AddCopyOfShaderInput(shaderKeyword, keywordInsertIndex);
  181. // Update the keyword nodes that depends on the copied node
  182. foreach (var node in dependentNodeList)
  183. {
  184. if (node is KeywordNode propertyNode)
  185. {
  186. propertyNode.owner = graphData;
  187. propertyNode.keyword = copiedKeyword;
  188. }
  189. }
  190. copiedShaderInput = copiedKeyword;
  191. break;
  192. case ShaderDropdown shaderDropdown:
  193. // InsertIndex gets passed in relative to the blackboard position of an item overall,
  194. // and not relative to the array sizes of the properties/keywords/dropdowns
  195. var dropdownInsertIndex = insertIndex - graphData.properties.Count() - graphData.keywords.Count();
  196. dropdownInsertIndex = Mathf.Clamp(dropdownInsertIndex, -1, graphData.dropdowns.Count() - 1);
  197. var copiedDropdown = (ShaderDropdown)graphData.AddCopyOfShaderInput(shaderDropdown, dropdownInsertIndex);
  198. // Update the dropdown nodes that depends on the copied node
  199. foreach (var node in dependentNodeList)
  200. {
  201. if (node is DropdownNode propertyNode)
  202. {
  203. propertyNode.owner = graphData;
  204. propertyNode.dropdown = copiedDropdown;
  205. }
  206. }
  207. copiedShaderInput = copiedDropdown;
  208. break;
  209. default:
  210. throw new ArgumentOutOfRangeException();
  211. }
  212. if (copiedShaderInput != null)
  213. {
  214. // If specific category to copy to is provided, find and use it
  215. foreach (var category in graphData.categories)
  216. {
  217. if (category.categoryGuid == containingCategoryGuid)
  218. {
  219. // Ensures that the new item gets added after the item it was duplicated from
  220. insertIndex += 1;
  221. // If the source item was already the last item in list, just add to end of list
  222. if (insertIndex >= category.childCount)
  223. insertIndex = -1;
  224. graphData.InsertItemIntoCategory(category.objectId, copiedShaderInput, insertIndex);
  225. return;
  226. }
  227. }
  228. // Else, add to default category
  229. graphData.categories.First().InsertItemIntoCategory(copiedShaderInput);
  230. }
  231. }
  232. public Action<GraphData> modifyGraphDataAction => CopyShaderInput;
  233. public IEnumerable<AbstractMaterialNode> dependentNodeList { get; set; } = new List<AbstractMaterialNode>();
  234. public BlackboardItem shaderInputToCopy { get; set; }
  235. public BlackboardItem copiedShaderInput { get; set; }
  236. public string containingCategoryGuid { get; set; }
  237. public int insertIndex { get; set; } = -1;
  238. }
  239. class AddCategoryAction : IGraphDataAction
  240. {
  241. void AddCategory(GraphData graphData)
  242. {
  243. AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out AddCategoryAction");
  244. graphData.owner.RegisterCompleteObjectUndo("Add Category");
  245. // If categoryDataReference is not null, directly add it to graphData
  246. if (categoryDataReference == null)
  247. categoryDataReference = new CategoryData(categoryName, childObjects);
  248. graphData.AddCategory(categoryDataReference);
  249. }
  250. public Action<GraphData> modifyGraphDataAction => AddCategory;
  251. // Direct reference to the categoryData to use if it is specified
  252. public CategoryData categoryDataReference { get; set; }
  253. public string categoryName { get; set; } = String.Empty;
  254. public List<ShaderInput> childObjects { get; set; }
  255. }
  256. class MoveCategoryAction : IGraphDataAction
  257. {
  258. void MoveCategory(GraphData graphData)
  259. {
  260. AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out MoveCategoryAction");
  261. graphData.owner.RegisterCompleteObjectUndo("Move Category");
  262. // Handling for out of range moves is slightly different, but otherwise we need to reverse for insertion order.
  263. var guids = newIndexValue >= graphData.categories.Count() ? categoryGuids : categoryGuids.Reverse<string>();
  264. foreach (var guid in categoryGuids)
  265. {
  266. var cat = graphData.categories.FirstOrDefault(c => c.categoryGuid == guid);
  267. graphData.MoveCategory(cat, newIndexValue);
  268. }
  269. }
  270. public Action<GraphData> modifyGraphDataAction => MoveCategory;
  271. // Reference to the shader input being modified
  272. internal List<string> categoryGuids { get; set; }
  273. internal int newIndexValue { get; set; }
  274. }
  275. class AddItemToCategoryAction : IGraphDataAction
  276. {
  277. public enum AddActionSource
  278. {
  279. Default,
  280. DragDrop
  281. }
  282. void AddItemsToCategory(GraphData graphData)
  283. {
  284. AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out AddItemToCategoryAction");
  285. graphData.owner.RegisterCompleteObjectUndo("Add Item to Category");
  286. graphData.InsertItemIntoCategory(categoryGuid, itemToAdd, indexToAddItemAt);
  287. }
  288. public Action<GraphData> modifyGraphDataAction => AddItemsToCategory;
  289. public string categoryGuid { get; set; }
  290. public ShaderInput itemToAdd { get; set; }
  291. // 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
  292. public int indexToAddItemAt { get; set; } = -1;
  293. public AddActionSource addActionSource { get; set; }
  294. }
  295. class CopyCategoryAction : IGraphDataAction
  296. {
  297. void CopyCategory(GraphData graphData)
  298. {
  299. AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out CopyCategoryAction");
  300. AssertHelpers.IsNotNull(categoryToCopyReference, "CategoryToCopyReference is null while carrying out CopyCategoryAction");
  301. // This is called by MaterialGraphView currently, no need to repeat it here, though ideally it would live here
  302. //graphData.owner.RegisterCompleteObjectUndo("Copy Category");
  303. newCategoryDataReference = graphData.CopyCategory(categoryToCopyReference);
  304. }
  305. // Reference to the new category created as a copy
  306. public CategoryData newCategoryDataReference { get; set; }
  307. // After category has been copied, store reference to it
  308. public CategoryData categoryToCopyReference { get; set; }
  309. public Action<GraphData> modifyGraphDataAction => CopyCategory;
  310. }
  311. class ShaderVariantLimitAction : IGraphDataAction
  312. {
  313. public int currentVariantCount { get; set; } = 0;
  314. public int maxVariantCount { get; set; } = 0;
  315. public ShaderVariantLimitAction(int currentVariantCount, int maxVariantCount)
  316. {
  317. this.maxVariantCount = maxVariantCount;
  318. this.currentVariantCount = currentVariantCount;
  319. }
  320. // There's no action actually performed on the graph, but we need to implement this as a valid function
  321. public Action<GraphData> modifyGraphDataAction => Empty;
  322. void Empty(GraphData graphData)
  323. {
  324. }
  325. }
  326. class BlackboardController : SGViewController<GraphData, BlackboardViewModel>
  327. {
  328. // Type changes (adds/removes of Types) only happen after a full assembly reload so its safe to make this static
  329. static IList<Type> s_ShaderInputTypes;
  330. static BlackboardController()
  331. {
  332. var shaderInputTypes = TypeCache.GetTypesWithAttribute<BlackboardInputInfo>().ToList();
  333. // Sort the ShaderInput by priority using the BlackboardInputInfo attribute
  334. shaderInputTypes.Sort((s1, s2) =>
  335. {
  336. var info1 = Attribute.GetCustomAttribute(s1, typeof(BlackboardInputInfo)) as BlackboardInputInfo;
  337. var info2 = Attribute.GetCustomAttribute(s2, typeof(BlackboardInputInfo)) as BlackboardInputInfo;
  338. if (info1.priority == info2.priority)
  339. return (info1.name ?? s1.Name).CompareTo(info2.name ?? s2.Name);
  340. else
  341. return info1.priority.CompareTo(info2.priority);
  342. });
  343. s_ShaderInputTypes = shaderInputTypes.ToList();
  344. }
  345. BlackboardCategoryController m_DefaultCategoryController = null;
  346. Dictionary<string, BlackboardCategoryController> m_BlackboardCategoryControllers = new Dictionary<string, BlackboardCategoryController>();
  347. protected SGBlackboard m_Blackboard;
  348. internal SGBlackboard blackboard
  349. {
  350. get => m_Blackboard;
  351. private set => m_Blackboard = value;
  352. }
  353. public string GetFirstSelectedCategoryGuid()
  354. {
  355. if (m_Blackboard == null)
  356. {
  357. return string.Empty;
  358. }
  359. var copiedSelectionList = new List<ISelectable>(m_Blackboard.selection);
  360. var selectedCategories = new List<SGBlackboardCategory>();
  361. var selectedCategoryGuid = String.Empty;
  362. for (int i = 0; i < copiedSelectionList.Count; i++)
  363. {
  364. var selectable = copiedSelectionList[i];
  365. if (selectable is SGBlackboardCategory category)
  366. {
  367. selectedCategories.Add(selectable as SGBlackboardCategory);
  368. }
  369. }
  370. if (selectedCategories.Any())
  371. {
  372. selectedCategoryGuid = selectedCategories[0].viewModel.associatedCategoryGuid;
  373. }
  374. return selectedCategoryGuid;
  375. }
  376. void InitializeViewModel(bool useDropdowns)
  377. {
  378. // Clear the view model
  379. ViewModel.ResetViewModelData();
  380. ViewModel.subtitle = BlackboardUtils.FormatPath(Model.path);
  381. BlackboardShaderInputOrder propertyTypesOrder = new BlackboardShaderInputOrder();
  382. // Property data first
  383. foreach (var shaderInputType in s_ShaderInputTypes)
  384. {
  385. if (shaderInputType.IsAbstract)
  386. continue;
  387. var info = Attribute.GetCustomAttribute(shaderInputType, typeof(BlackboardInputInfo)) as BlackboardInputInfo;
  388. string name = info?.name ?? ObjectNames.NicifyVariableName(shaderInputType.Name.Replace("ShaderProperty", ""));
  389. // QUICK FIX TO DEAL WITH DEPRECATED COLOR PROPERTY
  390. if (name.Equals("Color", StringComparison.InvariantCultureIgnoreCase) && ShaderGraphPreferences.allowDeprecatedBehaviors)
  391. {
  392. propertyTypesOrder.isKeyword = false;
  393. propertyTypesOrder.deprecatedPropertyName = name;
  394. propertyTypesOrder.version = ColorShaderProperty.deprecatedVersion;
  395. ViewModel.propertyNameToAddActionMap.Add($"Color (Legacy v0)", AddShaderInputAction.AddDeprecatedPropertyAction(propertyTypesOrder));
  396. ViewModel.propertyNameToAddActionMap.Add(name, AddShaderInputAction.AddPropertyAction(shaderInputType));
  397. }
  398. else
  399. ViewModel.propertyNameToAddActionMap.Add(name, AddShaderInputAction.AddPropertyAction(shaderInputType));
  400. }
  401. // Default Keywords next
  402. BlackboardShaderInputOrder keywordTypesOrder = new BlackboardShaderInputOrder();
  403. keywordTypesOrder.isKeyword = true;
  404. keywordTypesOrder.keywordType = KeywordType.Boolean;
  405. ViewModel.defaultKeywordNameToAddActionMap.Add("Boolean", AddShaderInputAction.AddKeywordAction(keywordTypesOrder));
  406. keywordTypesOrder.keywordType = KeywordType.Enum;
  407. ViewModel.defaultKeywordNameToAddActionMap.Add("Enum", AddShaderInputAction.AddKeywordAction(keywordTypesOrder));
  408. // Built-In Keywords after that
  409. foreach (var builtinKeywordDescriptor in KeywordUtil.GetBuiltinKeywordDescriptors())
  410. {
  411. var keyword = ShaderKeyword.CreateBuiltInKeyword(builtinKeywordDescriptor);
  412. // Do not allow user to add built-in keywords that conflict with user-made keywords that have the same reference name or display name
  413. if (Model.keywords.Any(x => x.referenceName == keyword.referenceName || x.displayName == keyword.displayName))
  414. {
  415. ViewModel.disabledKeywordNameList.Add(keyword.displayName);
  416. }
  417. else
  418. {
  419. keywordTypesOrder.builtInKeyword = (ShaderKeyword)keyword.Copy();
  420. ViewModel.builtInKeywordNameToAddActionMap.Add(keyword.displayName, AddShaderInputAction.AddKeywordAction(keywordTypesOrder));
  421. }
  422. }
  423. if (useDropdowns)
  424. {
  425. BlackboardShaderInputOrder dropdownsOrder = new BlackboardShaderInputOrder();
  426. dropdownsOrder.isDropdown = true;
  427. ViewModel.defaultDropdownNameToAdd = new Tuple<string, IGraphDataAction>("Dropdown", AddShaderInputAction.AddDropdownAction(dropdownsOrder));
  428. }
  429. // Category data last
  430. var defaultNewCategoryReference = new CategoryData("Category");
  431. ViewModel.addCategoryAction = new AddCategoryAction() { categoryDataReference = defaultNewCategoryReference };
  432. ViewModel.requestModelChangeAction = this.RequestModelChange;
  433. ViewModel.categoryInfoList.AddRange(DataStore.State.categories.ToList());
  434. }
  435. internal BlackboardController(GraphData model, BlackboardViewModel inViewModel, GraphDataStore graphDataStore)
  436. : base(model, inViewModel, graphDataStore)
  437. {
  438. // TODO: hide this more generically for category types.
  439. bool useDropdowns = model.isSubGraph;
  440. InitializeViewModel(useDropdowns);
  441. blackboard = new SGBlackboard(ViewModel, this);
  442. // Add default category at the top of the blackboard (create it if it doesn't exist already)
  443. var existingDefaultCategory = DataStore.State.categories.FirstOrDefault();
  444. if (existingDefaultCategory != null && existingDefaultCategory.IsNamedCategory() == false)
  445. {
  446. AddBlackboardCategory(graphDataStore, existingDefaultCategory);
  447. }
  448. else
  449. {
  450. // 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)
  451. var uncategorizedBlackboardItems = new List<ShaderInput>();
  452. foreach (var shaderProperty in DataStore.State.properties)
  453. if (IsInputUncategorized(shaderProperty))
  454. uncategorizedBlackboardItems.Add(shaderProperty);
  455. foreach (var shaderKeyword in DataStore.State.keywords)
  456. if (IsInputUncategorized(shaderKeyword))
  457. uncategorizedBlackboardItems.Add(shaderKeyword);
  458. if (useDropdowns)
  459. {
  460. foreach (var shaderDropdown in DataStore.State.dropdowns)
  461. if (IsInputUncategorized(shaderDropdown))
  462. uncategorizedBlackboardItems.Add(shaderDropdown);
  463. }
  464. var addCategoryAction = new AddCategoryAction();
  465. addCategoryAction.categoryDataReference = CategoryData.DefaultCategory(uncategorizedBlackboardItems);
  466. graphDataStore.Dispatch(addCategoryAction);
  467. }
  468. // Get the reference to default category controller after its been added
  469. m_DefaultCategoryController = m_BlackboardCategoryControllers.Values.FirstOrDefault();
  470. AssertHelpers.IsNotNull(m_DefaultCategoryController, "Failed to instantiate default category.");
  471. // Handle loaded-in categories from graph first, skipping the first/default category
  472. foreach (var categoryData in ViewModel.categoryInfoList.Skip(1))
  473. {
  474. AddBlackboardCategory(graphDataStore, categoryData);
  475. }
  476. }
  477. internal string editorPrefsBaseKey => "unity.shadergraph." + DataStore.State.objectId;
  478. BlackboardCategoryController AddBlackboardCategory(GraphDataStore graphDataStore, CategoryData categoryInfo)
  479. {
  480. var blackboardCategoryViewModel = new BlackboardCategoryViewModel();
  481. blackboardCategoryViewModel.parentView = blackboard;
  482. blackboardCategoryViewModel.requestModelChangeAction = ViewModel.requestModelChangeAction;
  483. blackboardCategoryViewModel.name = categoryInfo.name;
  484. blackboardCategoryViewModel.associatedCategoryGuid = categoryInfo.categoryGuid;
  485. blackboardCategoryViewModel.isExpanded = EditorPrefs.GetBool($"{editorPrefsBaseKey}.{categoryInfo.categoryGuid}.{ChangeCategoryIsExpandedAction.kEditorPrefKey}", true);
  486. var blackboardCategoryController = new BlackboardCategoryController(categoryInfo, blackboardCategoryViewModel, graphDataStore);
  487. if (m_BlackboardCategoryControllers.ContainsKey(categoryInfo.categoryGuid) == false)
  488. {
  489. m_BlackboardCategoryControllers.Add(categoryInfo.categoryGuid, blackboardCategoryController);
  490. m_DefaultCategoryController = m_BlackboardCategoryControllers.Values.FirstOrDefault();
  491. }
  492. else
  493. {
  494. AssertHelpers.Fail("Failed to add category controller due to category with same GUID already having been added.");
  495. return null;
  496. }
  497. return blackboardCategoryController;
  498. }
  499. // Creates controller, view and view model for a blackboard item and adds the view to the specified index in the category
  500. SGBlackboardRow InsertBlackboardRow(BlackboardItem shaderInput, int insertionIndex = -1)
  501. {
  502. return m_DefaultCategoryController.InsertBlackboardRow(shaderInput, insertionIndex);
  503. }
  504. public void UpdateBlackboardTitle(string newTitle)
  505. {
  506. ViewModel.title = newTitle;
  507. blackboard.title = ViewModel.title;
  508. }
  509. protected override void RequestModelChange(IGraphDataAction changeAction)
  510. {
  511. DataStore.Dispatch(changeAction);
  512. }
  513. // Called by GraphDataStore.Subscribe after the model has been changed
  514. protected override void ModelChanged(GraphData graphData, IGraphDataAction changeAction)
  515. {
  516. // Reconstruct view-model first
  517. // TODO: hide this more generically for category types.
  518. bool useDropdowns = graphData.isSubGraph;
  519. InitializeViewModel(useDropdowns);
  520. var graphView = ViewModel.parentView as MaterialGraphView;
  521. switch (changeAction)
  522. {
  523. // If newly added input doesn't belong to any of the user-made categories, add it to the default category at top of blackboard
  524. case AddShaderInputAction addBlackboardItemAction:
  525. if (IsInputUncategorized(addBlackboardItemAction.shaderInputReference))
  526. {
  527. var blackboardRow = InsertBlackboardRow(addBlackboardItemAction.shaderInputReference);
  528. if (blackboardRow != null)
  529. {
  530. var propertyView = blackboardRow.Q<SGBlackboardField>();
  531. if (addBlackboardItemAction.addInputActionType == AddShaderInputAction.AddActionSource.AddMenu)
  532. propertyView.OpenTextEditor();
  533. }
  534. }
  535. break;
  536. // Need to handle deletion of shader inputs here as opposed to BlackboardCategoryController, as currently,
  537. // once removed from the categories there is no way to associate an input with the category that owns it
  538. case DeleteShaderInputAction deleteShaderInputAction:
  539. foreach (var shaderInput in deleteShaderInputAction.shaderInputsToDelete)
  540. RemoveInputFromBlackboard(shaderInput);
  541. break;
  542. case HandleUndoRedoAction handleUndoRedoAction:
  543. ClearBlackboardCategories();
  544. foreach (var categoryData in graphData.addedCategories)
  545. AddBlackboardCategory(DataStore, categoryData);
  546. m_DefaultCategoryController = m_BlackboardCategoryControllers.Values.FirstOrDefault();
  547. break;
  548. case CopyShaderInputAction copyShaderInputAction:
  549. // 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
  550. if (copyShaderInputAction.copiedShaderInput != null && IsInputUncategorized(copyShaderInputAction.copiedShaderInput))
  551. {
  552. var blackboardRow = InsertBlackboardRow(copyShaderInputAction.copiedShaderInput, copyShaderInputAction.insertIndex);
  553. var propertyView = blackboardRow.Q<SGBlackboardField>();
  554. graphView?.AddToSelectionNoUndoRecord(propertyView);
  555. }
  556. break;
  557. case AddCategoryAction addCategoryAction:
  558. AddBlackboardCategory(DataStore, addCategoryAction.categoryDataReference);
  559. // Iterate through anything that is selected currently
  560. foreach (var selectedElement in blackboard.selection.ToList())
  561. {
  562. if (selectedElement is SGBlackboardField { userData: ShaderInput shaderInput })
  563. {
  564. // If a blackboard item is selected, first remove it from the blackboard
  565. RemoveInputFromBlackboard(shaderInput);
  566. // Then add input to the new category
  567. var addItemToCategoryAction = new AddItemToCategoryAction();
  568. addItemToCategoryAction.categoryGuid = addCategoryAction.categoryDataReference.categoryGuid;
  569. addItemToCategoryAction.itemToAdd = shaderInput;
  570. DataStore.Dispatch(addItemToCategoryAction);
  571. }
  572. }
  573. break;
  574. case DeleteCategoryAction deleteCategoryAction:
  575. // Clean up deleted categories
  576. foreach (var categoryGUID in deleteCategoryAction.categoriesToRemoveGuids)
  577. {
  578. RemoveBlackboardCategory(categoryGUID);
  579. }
  580. break;
  581. case MoveCategoryAction moveCategoryAction:
  582. ClearBlackboardCategories();
  583. foreach (var categoryData in ViewModel.categoryInfoList)
  584. AddBlackboardCategory(graphData.owner.graphDataStore, categoryData);
  585. break;
  586. case CopyCategoryAction copyCategoryAction:
  587. var blackboardCategory = AddBlackboardCategory(graphData.owner.graphDataStore, copyCategoryAction.newCategoryDataReference);
  588. if (blackboardCategory != null)
  589. graphView?.AddToSelectionNoUndoRecord(blackboardCategory.blackboardCategoryView);
  590. break;
  591. case ShaderVariantLimitAction shaderVariantLimitAction:
  592. blackboard.SetCurrentVariantUsage(shaderVariantLimitAction.currentVariantCount, shaderVariantLimitAction.maxVariantCount);
  593. break;
  594. }
  595. // Lets all event handlers this controller owns/manages know that the model has changed
  596. // Usually this is to update views and make them reconstruct themself from updated view-model
  597. //NotifyChange(changeAction);
  598. // Let child controllers know about changes to this controller so they may update themselves in turn
  599. //ApplyChanges();
  600. }
  601. void RemoveInputFromBlackboard(ShaderInput shaderInput)
  602. {
  603. // Check if input is in one of the categories
  604. foreach (var controller in m_BlackboardCategoryControllers.Values)
  605. {
  606. var blackboardRow = controller.FindBlackboardRow(shaderInput);
  607. if (blackboardRow != null)
  608. {
  609. controller.RemoveBlackboardRow(shaderInput);
  610. return;
  611. }
  612. }
  613. }
  614. bool IsInputUncategorized(ShaderInput shaderInput)
  615. {
  616. // Skip the first category controller as that is guaranteed to be the default category
  617. foreach (var categoryController in m_BlackboardCategoryControllers.Values.Skip(1))
  618. {
  619. if (categoryController.IsInputInCategory(shaderInput))
  620. return false;
  621. }
  622. return true;
  623. }
  624. public SGBlackboardCategory GetBlackboardCategory(string inputGuid)
  625. {
  626. foreach (var categoryController in m_BlackboardCategoryControllers.Values)
  627. {
  628. if (categoryController.Model.categoryGuid == inputGuid)
  629. return categoryController.blackboardCategoryView;
  630. }
  631. return null;
  632. }
  633. public SGBlackboardRow GetBlackboardRow(ShaderInput blackboardItem)
  634. {
  635. foreach (var categoryController in m_BlackboardCategoryControllers.Values)
  636. {
  637. var blackboardRow = categoryController.FindBlackboardRow(blackboardItem);
  638. if (blackboardRow != null)
  639. return blackboardRow;
  640. }
  641. return null;
  642. }
  643. int numberOfCategories => m_BlackboardCategoryControllers.Count;
  644. // Gets the index after the currently selected shader input for pasting properties into this graph
  645. internal int GetInsertionIndexForPaste()
  646. {
  647. if (blackboard?.selection == null || blackboard.selection.Count == 0)
  648. {
  649. return 0;
  650. }
  651. foreach (ISelectable selection in blackboard.selection)
  652. {
  653. if (selection is SGBlackboardField blackboardPropertyView)
  654. {
  655. SGBlackboardRow row = blackboardPropertyView.GetFirstAncestorOfType<SGBlackboardRow>();
  656. SGBlackboardCategory category = blackboardPropertyView.GetFirstAncestorOfType<SGBlackboardCategory>();
  657. if (row == null || category == null)
  658. continue;
  659. int blackboardFieldIndex = category.IndexOf(row);
  660. return blackboardFieldIndex;
  661. }
  662. }
  663. return 0;
  664. }
  665. void RemoveBlackboardCategory(string categoryGUID)
  666. {
  667. m_BlackboardCategoryControllers.TryGetValue(categoryGUID, out var blackboardCategoryController);
  668. if (blackboardCategoryController != null)
  669. {
  670. blackboardCategoryController.Dispose();
  671. m_BlackboardCategoryControllers.Remove(categoryGUID);
  672. }
  673. else
  674. AssertHelpers.Fail("Tried to remove a category that doesn't exist. ");
  675. }
  676. public override void Dispose()
  677. {
  678. if (m_Blackboard == null)
  679. return;
  680. base.Dispose();
  681. m_DefaultCategoryController = null;
  682. ClearBlackboardCategories();
  683. m_Blackboard?.Dispose();
  684. m_Blackboard = null;
  685. }
  686. void ClearBlackboardCategories()
  687. {
  688. foreach (var categoryController in m_BlackboardCategoryControllers.Values)
  689. {
  690. categoryController.Dispose();
  691. }
  692. m_BlackboardCategoryControllers.Clear();
  693. }
  694. // Meant to be used by UI testing in order to clear blackboard state
  695. internal void ResetBlackboardState()
  696. {
  697. ClearBlackboardCategories();
  698. var addCategoryAction = new AddCategoryAction();
  699. addCategoryAction.categoryDataReference = CategoryData.DefaultCategory();
  700. DataStore.Dispatch(addCategoryAction);
  701. }
  702. }
  703. }