-
Notifications
You must be signed in to change notification settings - Fork 866
Expand file tree
/
Copy pathBlackboardController.cs
More file actions
820 lines (692 loc) · 36.9 KB
/
BlackboardController.cs
File metadata and controls
820 lines (692 loc) · 36.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
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?.Length == 0)
{
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>();
protected 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.Count > 0)
{
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 (Legacy v0)", 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);
}
}
}