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,8 @@
fileFormatVersion: 2
guid: 465f456071d65c648a34adddf33057f7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,22 @@
using System;
namespace UnityEditor.ShaderGraph
{
[AttributeUsage(AttributeTargets.Class)]
class BlackboardInputInfo : Attribute
{
public float priority;
public string name;
/// <summary>
/// Provide additional information to provide the blackboard for order and name of the ShaderInput item.
/// </summary>
/// <param name="priority">Priority of the item, higher values will result in lower positions in the menu.</param>
/// <param name="name">Name of the item. If null, the class name of the item will be used instead.</param>
public BlackboardInputInfo(float priority, string name = null)
{
this.priority = priority;
this.name = name;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ca3677c253614a948be3cf8f0d17e254
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing
{
static class BlackboardUtils
{
internal static int GetInsertionIndex(VisualElement owner, Vector2 position, IEnumerable<VisualElement> children)
{
var index = -1;
if (owner.ContainsPoint(position))
{
index = 0;
foreach (VisualElement child in children)
{
Rect rect = child.layout;
if (position.y > (rect.y + rect.height / 2))
{
++index;
}
else
{
break;
}
}
}
return index;
}
internal static string FormatPath(string path)
{
if (string.IsNullOrEmpty(path))
return "—";
return path;
}
internal static string SanitizePath(string path)
{
var splitString = path.Split('/');
List<string> newStrings = new List<string>();
foreach (string s in splitString)
{
var str = s.Trim();
if (!string.IsNullOrEmpty(str))
{
newStrings.Add(str);
}
}
return string.Join("/", newStrings.ToArray());
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 956e673aa3cb4690b9c44d970ffd8e65
timeCreated: 1611700923

View file

@ -0,0 +1,582 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.Experimental.GraphView;
using UnityEditor.ShaderGraph.Drawing.Views;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.ShaderGraph;
namespace UnityEditor.ShaderGraph.Drawing
{
class BlackboardGroupInfo
{
[SerializeField]
SerializableGuid m_Guid = new SerializableGuid();
internal Guid guid => m_Guid.guid;
[SerializeField]
string m_GroupName;
internal string GroupName
{
get => m_GroupName;
set => m_GroupName = value;
}
BlackboardGroupInfo()
{
}
}
class SGBlackboard : GraphSubWindow, ISGControlledElement<BlackboardController>
{
VisualElement m_ScrollBoundaryTop;
VisualElement m_ScrollBoundaryBottom;
VisualElement m_BottomResizer;
TextField m_PathLabelTextField;
public VisualElement m_VariantExceededHelpBox;
// --- Begin ISGControlledElement implementation
public void OnControllerChanged(ref SGControllerChangedEvent e)
{
}
public void OnControllerEvent(SGControllerEvent e)
{
}
public void SetCurrentVariantUsage(int currentVariantCount, int maxVariantCount)
{
if (currentVariantCount < maxVariantCount && m_VariantExceededHelpBox != null)
{
RemoveAt(0);
m_VariantExceededHelpBox = null;
}
else if (maxVariantCount <= currentVariantCount && m_VariantExceededHelpBox == null)
{
var helpBox = HelpBoxRow.CreateVariantLimitHelpBox(currentVariantCount, maxVariantCount);
m_VariantExceededHelpBox = helpBox;
Insert(0, helpBox);
}
}
public BlackboardController controller
{
get => m_Controller;
set
{
if (m_Controller != value)
{
if (m_Controller != null)
{
m_Controller.UnregisterHandler(this);
}
m_Controller = value;
if (m_Controller != null)
{
m_Controller.RegisterHandler(this);
}
}
}
}
SGController ISGControlledElement.controller => m_Controller;
// --- ISGControlledElement implementation
BlackboardController m_Controller;
BlackboardViewModel m_ViewModel;
BlackboardViewModel ViewModel
{
get => m_ViewModel;
set => m_ViewModel = value;
}
// List of user-made blackboard category views
IList<SGBlackboardCategory> m_BlackboardCategories = new List<SGBlackboardCategory>();
bool m_ScrollToTop = false;
bool m_ScrollToBottom = false;
bool m_EditPathCancelled = false;
bool m_IsUserDraggingItems = false;
int m_InsertIndex = -1;
const int k_DraggedPropertyScrollSpeed = 6;
public override string windowTitle => "Blackboard";
public override string elementName => "SGBlackboard";
public override string styleName => "SGBlackboard";
public override string UxmlName => "Blackboard/SGBlackboard";
public override string layoutKey => "UnityEditor.ShaderGraph.Blackboard";
Action addItemRequested { get; set; }
internal Action hideDragIndicatorAction { get; set; }
GenericMenu m_AddBlackboardItemMenu;
internal GenericMenu addBlackboardItemMenu => m_AddBlackboardItemMenu;
VisualElement m_DragIndicator;
public SGBlackboard(BlackboardViewModel viewModel, BlackboardController controller) : base(viewModel)
{
ViewModel = viewModel;
this.controller = controller;
InitializeAddBlackboardItemMenu();
// By default dock blackboard to left of graph window
windowDockingLayout.dockingLeft = true;
if (m_MainContainer.Q(name: "addButton") is Button addButton)
addButton.clickable.clicked += () =>
{
InitializeAddBlackboardItemMenu();
addItemRequested?.Invoke();
ShowAddPropertyMenu();
};
ParentView.RegisterCallback<FocusOutEvent>(evt => OnDragExitedEvent(new DragExitedEvent()));
m_TitleLabel.text = ViewModel.title;
m_SubTitleLabel.RegisterCallback<MouseDownEvent>(OnMouseDownEvent);
m_SubTitleLabel.text = ViewModel.subtitle;
m_PathLabelTextField = this.Q<TextField>("subTitleTextField");
m_PathLabelTextField.value = ViewModel.subtitle;
m_PathLabelTextField.visible = false;
m_PathLabelTextField.Q("unity-text-input").RegisterCallback<FocusOutEvent>(e => { OnEditPathTextFinished(); });
m_PathLabelTextField.Q("unity-text-input").RegisterCallback<KeyDownEvent>(OnPathTextFieldKeyPressed);
// These callbacks make sure the scroll boundary regions and drag indicator don't show up user is not dragging/dropping properties/categories
RegisterCallback<MouseUpEvent>(OnMouseUpEvent);
RegisterCallback<DragExitedEvent>(OnDragExitedEvent);
// Register drag callbacks
RegisterCallback<DragUpdatedEvent>(OnDragUpdatedEvent);
RegisterCallback<DragPerformEvent>(OnDragPerformEvent);
RegisterCallback<DragLeaveEvent>(OnDragLeaveEvent);
RegisterCallback<DragExitedEvent>(OnDragExitedEvent);
// This callback makes sure the drag indicator is shown again if user exits and then re-enters blackboard while dragging
RegisterCallback<MouseEnterEvent>(OnMouseEnterEvent);
m_ScrollBoundaryTop = m_MainContainer.Q(name: "scrollBoundaryTop");
m_ScrollBoundaryTop.RegisterCallback<MouseEnterEvent>(ScrollRegionTopEnter);
m_ScrollBoundaryTop.RegisterCallback<DragUpdatedEvent>(OnFieldDragUpdate);
m_ScrollBoundaryTop.RegisterCallback<MouseLeaveEvent>(ScrollRegionTopLeave);
m_ScrollBoundaryBottom = m_MainContainer.Q(name: "scrollBoundaryBottom");
m_ScrollBoundaryBottom.RegisterCallback<MouseEnterEvent>(ScrollRegionBottomEnter);
m_ScrollBoundaryBottom.RegisterCallback<DragUpdatedEvent>(OnFieldDragUpdate);
m_ScrollBoundaryBottom.RegisterCallback<MouseLeaveEvent>(ScrollRegionBottomLeave);
m_BottomResizer = m_MainContainer.Q("bottom-resize");
HideScrollBoundaryRegions();
// Sets delegate association so scroll boundary regions are hidden when a blackboard property is dropped into graph
if (ParentView is MaterialGraphView materialGraphView)
materialGraphView.blackboardFieldDropDelegate = HideScrollBoundaryRegions;
isWindowScrollable = true;
isWindowResizable = true;
focusable = true;
m_DragIndicator = new VisualElement();
m_DragIndicator.name = "categoryDragIndicator";
m_DragIndicator.style.position = Position.Absolute;
hierarchy.Add(m_DragIndicator);
SetCategoryDragIndicatorVisible(false);
}
void SetCategoryDragIndicatorVisible(bool visible)
{
if (visible && (m_DragIndicator.parent == null))
{
hierarchy.Add(m_DragIndicator);
m_DragIndicator.visible = true;
}
else if ((visible == false) && (m_DragIndicator.parent != null))
{
hierarchy.Remove(m_DragIndicator);
}
}
public void OnDragEnterEvent(DragEnterEvent evt)
{
if (!m_IsUserDraggingItems)
{
m_IsUserDraggingItems = true;
if (scrollableHeight > 0)
{
// Interferes with scrolling functionality of properties with the bottom scroll boundary
m_BottomResizer.style.visibility = Visibility.Hidden;
var contentElement = m_MainContainer.Q(name: "content");
scrollViewIndex = contentElement.IndexOf(m_ScrollView);
contentElement.Insert(scrollViewIndex, m_ScrollBoundaryTop);
scrollViewIndex = contentElement.IndexOf(m_ScrollView);
contentElement.Insert(scrollViewIndex + 1, m_ScrollBoundaryBottom);
}
// If there are any categories in the selection, show drag indicator, otherwise hide
SetCategoryDragIndicatorVisible(selection.OfType<SGBlackboardCategory>().Any());
}
}
public void OnDragExitedEvent(DragExitedEvent evt)
{
SetCategoryDragIndicatorVisible(false);
HideScrollBoundaryRegions();
}
void OnMouseEnterEvent(MouseEnterEvent evt)
{
if (m_IsUserDraggingItems && selection.OfType<SGBlackboardCategory>().Any())
SetCategoryDragIndicatorVisible(true);
}
void HideScrollBoundaryRegions()
{
m_BottomResizer.style.visibility = Visibility.Visible;
m_IsUserDraggingItems = false;
m_ScrollBoundaryTop.RemoveFromHierarchy();
m_ScrollBoundaryBottom.RemoveFromHierarchy();
}
int InsertionIndex(Vector2 pos)
{
VisualElement owner = contentContainer != null ? contentContainer : this;
Vector2 localPos = this.ChangeCoordinatesTo(owner, pos);
int index = BlackboardUtils.GetInsertionIndex(owner, localPos, Children());
// Clamps the index between the min and max of the child indices based on the mouse position relative to the categories on the y-axis (up/down)
// Checking for at least 2 children to make sure Children.First() and Children.Last() don't throw an exception
if (index == -1 && childCount >= 2)
{
index = localPos.y < Children().First().layout.yMin ? 0 :
localPos.y > Children().Last().layout.yMax ? childCount : -1;
}
// Don't allow the default category to be displaced
return Mathf.Clamp(index, 1, index);
}
void OnDragUpdatedEvent(DragUpdatedEvent evt)
{
var selection = DragAndDrop.GetGenericData("DragSelection") as List<ISelectable>;
if (selection == null)
{
SetCategoryDragIndicatorVisible(false);
return;
}
foreach (ISelectable selectedElement in selection)
{
var sourceItem = selectedElement as VisualElement;
// Don't allow user to move the default category
if (sourceItem is SGBlackboardCategory blackboardCategory && blackboardCategory.controller.Model.IsNamedCategory() == false)
{
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
return;
}
}
Vector2 localPosition = evt.localMousePosition;
m_InsertIndex = InsertionIndex(localPosition);
if (m_InsertIndex != -1)
{
float indicatorY = 0;
if (m_InsertIndex == childCount)
{
if (childCount > 0)
{
VisualElement lastChild = this[childCount - 1];
indicatorY = lastChild.ChangeCoordinatesTo(this, new Vector2(0, lastChild.layout.height + lastChild.resolvedStyle.marginBottom)).y;
}
else
{
indicatorY = this.contentRect.height;
}
}
else
{
VisualElement childAtInsertIndex = this[m_InsertIndex];
indicatorY = childAtInsertIndex.ChangeCoordinatesTo(this, new Vector2(0, -childAtInsertIndex.resolvedStyle.marginTop)).y;
}
m_DragIndicator.style.top = indicatorY - m_DragIndicator.resolvedStyle.height * 0.5f;
DragAndDrop.visualMode = DragAndDropVisualMode.Move;
}
else
{
SetCategoryDragIndicatorVisible(false);
}
evt.StopPropagation();
}
void OnDragPerformEvent(DragPerformEvent evt)
{
// Don't bubble up drop operations onto blackboard upto the graph view, as it leads to nodes being created without users knowledge behind the blackboard
evt.StopPropagation();
var selection = DragAndDrop.GetGenericData("DragSelection") as List<ISelectable>;
if (selection == null || selection.Count == 0)
{
SetCategoryDragIndicatorVisible(false);
return;
}
// Hide the category drag indicator if no categories in selection
if (!selection.OfType<SGBlackboardCategory>().Any())
{
SetCategoryDragIndicatorVisible(false);
}
Vector2 localPosition = evt.localMousePosition;
m_InsertIndex = InsertionIndex(localPosition);
// Any categories in the selection that are from other graphs, would have to be copied as opposed to moving the categories within the same graph
foreach (var item in selection.ToList())
{
if (item is SGBlackboardCategory category)
{
var selectedCategoryData = category.controller.Model;
bool doesCategoryExistInGraph = controller.Model.ContainsCategory(selectedCategoryData);
if (doesCategoryExistInGraph == false)
{
var copyCategoryAction = new CopyCategoryAction();
copyCategoryAction.categoryToCopyReference = selectedCategoryData;
ViewModel.requestModelChangeAction(copyCategoryAction);
selection.Remove(item);
// Remove any child inputs that belong to this category from the selection, to prevent duplicates from being copied onto the graph
foreach (var otherItem in selection.ToList())
{
if (otherItem is SGBlackboardField blackboardField && category.Contains(blackboardField))
selection.Remove(otherItem);
}
}
}
}
// Same as above, but for blackboard items (properties, keywords, dropdowns)
foreach (var item in selection.ToList())
{
if (item is SGBlackboardField blackboardField)
{
var selectedBlackboardItem = blackboardField.controller.Model;
bool doesInputExistInGraph = controller.Model.ContainsInput(selectedBlackboardItem);
if (doesInputExistInGraph == false)
{
var copyShaderInputAction = new CopyShaderInputAction();
copyShaderInputAction.shaderInputToCopy = selectedBlackboardItem;
ViewModel.requestModelChangeAction(copyShaderInputAction);
selection.Remove(item);
}
}
}
var moveCategoryAction = new MoveCategoryAction();
moveCategoryAction.newIndexValue = m_InsertIndex;
moveCategoryAction.categoryGuids = selection.OfType<SGBlackboardCategory>().OrderBy(sgcat => sgcat.GetPosition().y).Select(cat => cat.viewModel.associatedCategoryGuid).ToList();
ViewModel.requestModelChangeAction(moveCategoryAction);
SetCategoryDragIndicatorVisible(false);
}
void OnDragLeaveEvent(DragLeaveEvent evt)
{
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
SetCategoryDragIndicatorVisible(false);
m_InsertIndex = -1;
}
int scrollViewIndex { get; set; }
void ScrollRegionTopEnter(MouseEnterEvent mouseEnterEvent)
{
if (m_IsUserDraggingItems)
{
SetCategoryDragIndicatorVisible(false);
m_ScrollToTop = true;
m_ScrollToBottom = false;
}
}
void ScrollRegionTopLeave(MouseLeaveEvent mouseLeaveEvent)
{
if (m_IsUserDraggingItems)
{
m_ScrollToTop = false;
// If there are any categories in the selection, show drag indicator, otherwise hide
SetCategoryDragIndicatorVisible(selection.OfType<SGBlackboardCategory>().Any());
}
}
void ScrollRegionBottomEnter(MouseEnterEvent mouseEnterEvent)
{
if (m_IsUserDraggingItems)
{
SetCategoryDragIndicatorVisible(false);
m_ScrollToBottom = true;
m_ScrollToTop = false;
}
}
void ScrollRegionBottomLeave(MouseLeaveEvent mouseLeaveEvent)
{
if (m_IsUserDraggingItems)
{
m_ScrollToBottom = false;
// If there are any categories in the selection, show drag indicator, otherwise hide
SetCategoryDragIndicatorVisible(selection.OfType<SGBlackboardCategory>().Any());
}
}
void OnFieldDragUpdate(DragUpdatedEvent dragUpdatedEvent)
{
if (m_ScrollToTop)
m_ScrollView.scrollOffset = new Vector2(m_ScrollView.scrollOffset.x, Mathf.Clamp(m_ScrollView.scrollOffset.y - k_DraggedPropertyScrollSpeed, 0, scrollableHeight));
else if (m_ScrollToBottom)
m_ScrollView.scrollOffset = new Vector2(m_ScrollView.scrollOffset.x, Mathf.Clamp(m_ScrollView.scrollOffset.y + k_DraggedPropertyScrollSpeed, 0, scrollableHeight));
}
void InitializeAddBlackboardItemMenu()
{
m_AddBlackboardItemMenu = new GenericMenu();
if (ViewModel == null)
{
AssertHelpers.Fail("SGBlackboard: View Model is null.");
return;
}
// Add category at top, followed by separator
m_AddBlackboardItemMenu.AddItem(new GUIContent("Category"), false, () => ViewModel.requestModelChangeAction(ViewModel.addCategoryAction));
m_AddBlackboardItemMenu.AddSeparator($"/");
var selectedCategoryGuid = controller.GetFirstSelectedCategoryGuid();
foreach (var nameToAddActionTuple in ViewModel.propertyNameToAddActionMap)
{
string propertyName = nameToAddActionTuple.Key;
AddShaderInputAction addAction = nameToAddActionTuple.Value as AddShaderInputAction;
addAction.categoryToAddItemToGuid = selectedCategoryGuid;
m_AddBlackboardItemMenu.AddItem(new GUIContent(propertyName), false, () => ViewModel.requestModelChangeAction(addAction));
}
m_AddBlackboardItemMenu.AddSeparator($"/");
foreach (var nameToAddActionTuple in ViewModel.defaultKeywordNameToAddActionMap)
{
string defaultKeywordName = nameToAddActionTuple.Key;
AddShaderInputAction addAction = nameToAddActionTuple.Value as AddShaderInputAction;
addAction.categoryToAddItemToGuid = selectedCategoryGuid;
m_AddBlackboardItemMenu.AddItem(new GUIContent($"Keyword/{defaultKeywordName}"), false, () => ViewModel.requestModelChangeAction(addAction));
}
m_AddBlackboardItemMenu.AddSeparator($"Keyword/");
foreach (var nameToAddActionTuple in ViewModel.builtInKeywordNameToAddActionMap)
{
string builtInKeywordName = nameToAddActionTuple.Key;
AddShaderInputAction addAction = nameToAddActionTuple.Value as AddShaderInputAction;
addAction.categoryToAddItemToGuid = selectedCategoryGuid;
m_AddBlackboardItemMenu.AddItem(new GUIContent($"Keyword/{builtInKeywordName}"), false, () => ViewModel.requestModelChangeAction(addAction));
}
foreach (string disabledKeywordName in ViewModel.disabledKeywordNameList)
{
m_AddBlackboardItemMenu.AddDisabledItem(new GUIContent($"Keyword/{disabledKeywordName}"));
}
if (ViewModel.defaultDropdownNameToAdd != null)
{
string defaultDropdownName = ViewModel.defaultDropdownNameToAdd.Item1;
AddShaderInputAction addAction = ViewModel.defaultDropdownNameToAdd.Item2 as AddShaderInputAction;
addAction.categoryToAddItemToGuid = selectedCategoryGuid;
m_AddBlackboardItemMenu.AddItem(new GUIContent($"{defaultDropdownName}"), false, () => ViewModel.requestModelChangeAction(addAction));
}
foreach (string disabledDropdownName in ViewModel.disabledDropdownNameList)
{
m_AddBlackboardItemMenu.AddDisabledItem(new GUIContent(disabledDropdownName));
}
}
void ShowAddPropertyMenu()
{
m_AddBlackboardItemMenu.ShowAsContext();
}
void OnMouseUpEvent(MouseUpEvent evt)
{
this.HideScrollBoundaryRegions();
}
void OnMouseDownEvent(MouseDownEvent evt)
{
if (evt.clickCount == 2 && evt.button == (int)MouseButton.LeftMouse)
{
StartEditingPath();
evt.PreventDefault();
}
}
void StartEditingPath()
{
m_SubTitleLabel.visible = false;
m_PathLabelTextField.visible = true;
m_PathLabelTextField.value = m_SubTitleLabel.text;
m_PathLabelTextField.Q("unity-text-input").Focus();
m_PathLabelTextField.SelectAll();
}
void OnPathTextFieldKeyPressed(KeyDownEvent evt)
{
switch (evt.keyCode)
{
case KeyCode.Escape:
m_EditPathCancelled = true;
m_PathLabelTextField.Q("unity-text-input").Blur();
break;
case KeyCode.Return:
case KeyCode.KeypadEnter:
m_PathLabelTextField.Q("unity-text-input").Blur();
break;
default:
break;
}
}
void OnEditPathTextFinished()
{
m_SubTitleLabel.visible = true;
m_PathLabelTextField.visible = false;
var newPath = m_PathLabelTextField.text;
if (!m_EditPathCancelled && (newPath != m_SubTitleLabel.text))
{
newPath = BlackboardUtils.SanitizePath(newPath);
}
// Request graph path change action
var pathChangeAction = new ChangeGraphPathAction();
pathChangeAction.NewGraphPath = newPath;
ViewModel.requestModelChangeAction(pathChangeAction);
m_SubTitleLabel.text = BlackboardUtils.FormatPath(newPath);
m_EditPathCancelled = false;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ce596a3c8ef34fc6bcd82c059ad53e57
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,769 @@
using System;
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEditor.ShaderGraph.Drawing.Views;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine;
using UnityEngine.UIElements;
using System.Linq;
using ContextualMenuManipulator = UnityEngine.UIElements.ContextualMenuManipulator;
namespace UnityEditor.ShaderGraph.Drawing
{
sealed class SGBlackboardCategory : GraphElement, ISGControlledElement<BlackboardCategoryController>, ISelection, IComparable<SGBlackboardCategory>
{
// --- Begin ISGControlledElement implementation
public void OnControllerChanged(ref SGControllerChangedEvent e)
{
}
public void OnControllerEvent(SGControllerEvent e)
{
}
public BlackboardCategoryController controller
{
get => m_Controller;
set
{
if (m_Controller != value)
{
if (m_Controller != null)
{
m_Controller.UnregisterHandler(this);
}
m_Controller = value;
if (m_Controller != null)
{
m_Controller.RegisterHandler(this);
}
}
}
}
SGController ISGControlledElement.controller => m_Controller;
// --- ISGControlledElement implementation
BlackboardCategoryController m_Controller;
BlackboardCategoryViewModel m_ViewModel;
public BlackboardCategoryViewModel viewModel => m_ViewModel;
const string k_StylePath = "Styles/SGBlackboard";
const string k_UxmlPath = "UXML/Blackboard/SGBlackboardCategory";
VisualElement m_DragIndicator;
VisualElement m_MainContainer;
VisualElement m_Header;
Label m_TitleLabel;
Foldout m_Foldout;
TextField m_TextField;
internal TextField textField => m_TextField;
VisualElement m_RowsContainer;
int m_InsertIndex;
SGBlackboard blackboard => m_ViewModel.parentView as SGBlackboard;
bool m_IsDragInProgress;
bool m_WasHoverExpanded;
bool m_DroppedOnBottomEdge;
bool m_DroppedOnTopEdge;
bool m_RenameInProgress;
public delegate bool CanAcceptDropDelegate(ISelectable selected);
public CanAcceptDropDelegate canAcceptDrop { get; set; }
int InsertionIndex(Vector2 pos)
{
// For an empty category can always just insert at the start
if (this.childCount == 0)
return 0;
var blackboardRows = this.Query<SGBlackboardRow>().ToList();
for (int index = 0; index < blackboardRows.Count; index++)
{
var blackboardRow = blackboardRows[index];
var localPosition = this.ChangeCoordinatesTo(blackboardRow, pos);
if (blackboardRow.ContainsPoint(localPosition))
{
return index;
}
}
return -1;
}
static VisualElement FindCategoryDirectChild(SGBlackboardCategory blackboardCategory, VisualElement element)
{
VisualElement directChild = element;
while ((directChild != null) && (directChild != blackboardCategory))
{
if (directChild.parent == blackboardCategory)
{
return directChild;
}
directChild = directChild.parent;
}
return null;
}
internal SGBlackboardCategory(BlackboardCategoryViewModel categoryViewModel, BlackboardCategoryController inController)
{
m_ViewModel = categoryViewModel;
controller = inController;
userData = controller.Model;
// Setup VisualElement from Stylesheet and UXML file
var tpl = Resources.Load(k_UxmlPath) as VisualTreeAsset;
m_MainContainer = tpl.Instantiate();
m_MainContainer.AddToClassList("mainContainer");
m_Header = m_MainContainer.Q("categoryHeader");
m_TitleLabel = m_MainContainer.Q<Label>("categoryTitleLabel");
m_Foldout = m_MainContainer.Q<Foldout>("categoryTitleFoldout");
m_RowsContainer = m_MainContainer.Q("rowsContainer");
m_TextField = m_MainContainer.Q<TextField>("textField");
m_TextField.style.display = DisplayStyle.None;
hierarchy.Add(m_MainContainer);
m_DragIndicator = m_MainContainer.Q("dragIndicator");
m_DragIndicator.visible = false;
hierarchy.Add(m_DragIndicator);
capabilities |= Capabilities.Selectable | Capabilities.Movable | Capabilities.Droppable | Capabilities.Deletable | Capabilities.Renamable | Capabilities.Copiable;
ClearClassList();
AddToClassList("blackboardCategory");
// add the right click context menu
IManipulator contextMenuManipulator = new ContextualMenuManipulator(AddContextMenuOptions);
this.AddManipulator(contextMenuManipulator);
// add drag and drop manipulator
var selectionDropperManipulator = new SelectionDropper();
this.AddManipulator(selectionDropperManipulator);
RegisterCallback<MouseDownEvent>(OnMouseDownEvent);
var textInputElement = m_TextField.Q(TextField.textInputUssName);
textInputElement.RegisterCallback<FocusOutEvent>(e => { OnEditTextFinished(); });
// Register hover callbacks
RegisterCallback<MouseEnterEvent>(OnHoverStartEvent);
RegisterCallback<MouseLeaveEvent>(OnHoverEndEvent);
// Register drag callbacks
RegisterCallback<DragUpdatedEvent>(OnDragUpdatedEvent);
RegisterCallback<DragPerformEvent>(OnDragPerformEvent);
RegisterCallback<DragLeaveEvent>(OnDragLeaveEvent);
var styleSheet = Resources.Load<StyleSheet>(k_StylePath);
styleSheets.Add(styleSheet);
m_InsertIndex = -1;
// Update category title from view model
title = m_ViewModel.name;
this.viewDataKey = viewModel.associatedCategoryGuid;
if (String.IsNullOrEmpty(title))
{
m_Foldout.visible = false;
m_Foldout.RemoveFromHierarchy();
}
else
{
TryDoFoldout(m_ViewModel.isExpanded);
m_Foldout.RegisterCallback<ChangeEvent<bool>>(OnFoldoutToggle);
}
// Remove the header element if this is the default category
if (!controller.Model.IsNamedCategory())
headerVisible = false;
}
public override VisualElement contentContainer { get { return m_RowsContainer; } }
public override string title
{
get => m_TitleLabel.text;
set
{
m_TitleLabel.text = value;
if (m_TitleLabel.text == String.Empty)
{
AddToClassList("unnamed");
}
else
{
RemoveFromClassList("unnamed");
}
}
}
public bool headerVisible
{
get { return m_Header.parent != null; }
set
{
if (value == (m_Header.parent != null))
return;
if (value)
{
m_MainContainer.Add(m_Header);
}
else
{
m_MainContainer.Remove(m_Header);
}
}
}
void SetDragIndicatorVisible(bool visible)
{
m_DragIndicator.visible = visible;
}
public bool CategoryContains(List<ISelectable> selection)
{
if (selection == null)
return false;
// Look for at least one selected element in this category to accept drop
foreach (ISelectable selected in selection)
{
VisualElement selectedElement = selected as VisualElement;
if (selected != null && Contains(selectedElement))
{
if (canAcceptDrop == null || canAcceptDrop(selected))
return true;
}
}
return false;
}
void OnFoldoutToggle(ChangeEvent<bool> evt)
{
if (evt.previousValue != evt.newValue)
{
var isExpandedAction = new ChangeCategoryIsExpandedAction();
if (selection.Contains(this)) // expand all selected if the foldout is part of a selection
isExpandedAction.categoryGuids = selection.OfType<SGBlackboardCategory>().Select(s => s.viewModel.associatedCategoryGuid).ToList();
else
isExpandedAction.categoryGuids = new List<string>() { viewModel.associatedCategoryGuid };
isExpandedAction.isExpanded = evt.newValue;
isExpandedAction.editorPrefsBaseKey = blackboard.controller.editorPrefsBaseKey;
viewModel.requestModelChangeAction(isExpandedAction);
}
}
internal void TryDoFoldout(bool expand)
{
m_Foldout.SetValueWithoutNotify(expand);
if (!expand)
{
m_DragIndicator.visible = true;
m_RowsContainer.RemoveFromHierarchy();
}
else
{
m_DragIndicator.visible = false;
m_MainContainer.Add(m_RowsContainer);
}
var key = $"{blackboard.controller.editorPrefsBaseKey}.{viewDataKey}.{ChangeCategoryIsExpandedAction.kEditorPrefKey}";
EditorPrefs.SetBool(key, expand);
}
void OnMouseDownEvent(MouseDownEvent e)
{
// Handles double-click with left mouse, which should trigger a rename action on this category
if ((e.clickCount == 2) && e.button == (int)MouseButton.LeftMouse && IsRenamable())
{
OpenTextEditor();
e.PreventDefault();
}
else if (e.clickCount == 1 && e.button == (int)MouseButton.LeftMouse && IsRenamable())
{
// Select the child elements within this category (the field views)
var fieldViews = this.Query<SGBlackboardField>();
foreach (var child in fieldViews.ToList())
{
this.AddToSelection(child);
}
}
}
internal void OpenTextEditor()
{
m_TextField.SetValueWithoutNotify(title);
m_TextField.style.display = DisplayStyle.Flex;
m_TitleLabel.visible = false;
m_TextField.Q(TextField.textInputUssName).Focus();
m_TextField.SelectAll();
m_RenameInProgress = true;
}
void OnEditTextFinished()
{
m_TitleLabel.visible = true;
m_TextField.style.display = DisplayStyle.None;
if (title != m_TextField.text && String.IsNullOrWhiteSpace(m_TextField.text) == false)
{
var changeCategoryNameAction = new ChangeCategoryNameAction();
changeCategoryNameAction.newCategoryNameValue = m_TextField.text;
changeCategoryNameAction.categoryGuid = m_ViewModel.associatedCategoryGuid;
m_ViewModel.requestModelChangeAction(changeCategoryNameAction);
}
else
{
// Reset text field to original name
m_TextField.value = title;
}
m_RenameInProgress = false;
}
void OnHoverStartEvent(MouseEnterEvent evt)
{
AddToClassList("hovered");
if (selection.OfType<SGBlackboardField>().Any()
&& controller.Model.IsNamedCategory()
&& m_IsDragInProgress
&& !viewModel.isExpanded)
{
m_WasHoverExpanded = true;
TryDoFoldout(true);
}
}
void OnHoverEndEvent(MouseLeaveEvent evt)
{
if (m_WasHoverExpanded && m_IsDragInProgress)
{
m_WasHoverExpanded = false;
TryDoFoldout(false);
}
RemoveFromClassList("hovered");
}
void OnDragUpdatedEvent(DragUpdatedEvent evt)
{
var selection = DragAndDrop.GetGenericData("DragSelection") as List<ISelectable>;
// Don't show drag indicator if selection has categories,
// We don't want category drag & drop to be ambiguous with shader input drag & drop
if (selection.OfType<SGBlackboardCategory>().Any())
{
SetDragIndicatorVisible(false);
return;
}
// If can't find at least one blackboard field in the selection, don't update drag indicator
if (selection.OfType<SGBlackboardField>().Any() == false)
{
SetDragIndicatorVisible(false);
return;
}
m_IsDragInProgress = true;
Vector2 localPosition = evt.localMousePosition;
m_InsertIndex = InsertionIndex(localPosition);
if (m_InsertIndex != -1)
{
float indicatorY = 0;
bool inMoveRange = false;
// When category is empty
if (this.childCount == 0)
{
// This moves the indicator to the bottom of the category in case of an empty category
indicatorY = this.layout.height * 0.9f;
m_DragIndicator.style.marginBottom = 8;
inMoveRange = true;
}
else
{
m_DragIndicator.style.marginBottom = 0;
var relativePosition = new Vector2();
var childHeight = 0.0f;
VisualElement childAtInsertIndex = this[m_InsertIndex];
childHeight = childAtInsertIndex.layout.height;
relativePosition = this.ChangeCoordinatesTo(childAtInsertIndex, localPosition);
if (relativePosition.y > 0 && relativePosition.y < childHeight * 0.25f)
{
// Top Edge
inMoveRange = true;
indicatorY = childAtInsertIndex.ChangeCoordinatesTo(this, new Vector2(0, 0)).y;
m_DragIndicator.style.rotate = new StyleRotate(Rotate.None());
m_DroppedOnBottomEdge = false;
m_DroppedOnTopEdge = true;
}
else if (relativePosition.y > 0.75f * childHeight && relativePosition.y < childHeight)
{
// Bottom Edge
inMoveRange = true;
indicatorY = childAtInsertIndex.ChangeCoordinatesTo(this, new Vector2(0, 0)).y + childAtInsertIndex.layout.height;
//m_DragIndicator.style.rotate = new StyleRotate(new Rotate(-180));
m_DroppedOnBottomEdge = true;
m_DroppedOnTopEdge = false;
}
}
if (inMoveRange)
{
SetDragIndicatorVisible(true);
m_DragIndicator.style.width = layout.width;
var newPosition = indicatorY;
m_DragIndicator.style.top = newPosition;
}
else
SetDragIndicatorVisible(true);
}
else
SetDragIndicatorVisible(false);
if (m_InsertIndex != -1)
{
DragAndDrop.visualMode = DragAndDropVisualMode.Move;
}
}
void OnDragPerformEvent(DragPerformEvent evt)
{
var selection = DragAndDrop.GetGenericData("DragSelection") as List<ISelectable>;
m_IsDragInProgress = false;
// Don't show drag indicator if selection has categories,
// We don't want category drag & drop to be ambiguous with shader input drag & drop
if (selection.OfType<SGBlackboardCategory>().Any())
{
SetDragIndicatorVisible(false);
return;
}
if (m_InsertIndex == -1)
{
SetDragIndicatorVisible(false);
return;
}
// Map of containing categories to the actual dragged elements within them
SortedDictionary<SGBlackboardCategory, List<VisualElement>> draggedElements = new SortedDictionary<SGBlackboardCategory, List<VisualElement>>();
foreach (ISelectable selectedElement in selection)
{
var draggedElement = selectedElement as VisualElement;
if (draggedElement == null)
continue;
if (this.Contains(draggedElement))
{
if (draggedElements.ContainsKey(this))
draggedElements[this].Add(FindCategoryDirectChild(this, draggedElement));
else
draggedElements.Add(this, new List<VisualElement> { FindCategoryDirectChild(this, draggedElement) });
}
else
{
var otherCategory = draggedElement.GetFirstAncestorOfType<SGBlackboardCategory>();
if (otherCategory != null)
{
if (draggedElements.ContainsKey(otherCategory))
draggedElements[otherCategory].Add(FindCategoryDirectChild(otherCategory, draggedElement));
else
draggedElements.Add(otherCategory, new List<VisualElement> { FindCategoryDirectChild(otherCategory, draggedElement) });
}
}
}
if (draggedElements.Count == 0)
{
SetDragIndicatorVisible(false);
return;
}
foreach (var categoryToChildrenTuple in draggedElements)
{
var containingCategory = categoryToChildrenTuple.Key;
var childList = categoryToChildrenTuple.Value;
// Sorts the dragged elements from their relative order in their parent
childList.Sort((item1, item2) => containingCategory.IndexOf(item1).CompareTo(containingCategory.IndexOf(item2)));
}
int insertIndex = Mathf.Clamp(m_InsertIndex, 0, m_InsertIndex);
bool adjustedInsertIndex = false;
VisualElement lastInsertedElement = null;
/* Handles moving elements within a category */
foreach (var categoryToChildrenTuple in draggedElements)
{
var childList = categoryToChildrenTuple.Value;
VisualElement firstChild = childList.First();
foreach (var draggedElement in childList)
{
var blackboardField = draggedElement.Q<SGBlackboardField>();
ShaderInput shaderInput = null;
if (blackboardField != null)
shaderInput = blackboardField.controller.Model;
// Skip if this field is not contained by this category as we handle that in the next loop below
if (shaderInput == null || !this.Contains(blackboardField))
continue;
VisualElement categoryDirectChild = draggedElement;
int indexOfDraggedElement = IndexOf(categoryDirectChild);
bool listEndInsertion = false;
// Only find index for the first item
if (draggedElement == firstChild)
{
adjustedInsertIndex = true;
// Handles case of inserting after last item in list
if (insertIndex == childCount - 1 && m_DroppedOnBottomEdge)
{
listEndInsertion = true;
}
// Handles case of inserting after any item except the last in list
else if (m_DroppedOnBottomEdge)
insertIndex++;
insertIndex = Mathf.Clamp(insertIndex, 0, childCount - 1);
if (insertIndex != indexOfDraggedElement)
{
// If ever placing it at end of list, make sure to place after last item
if (listEndInsertion)
{
categoryDirectChild.PlaceInFront(this[insertIndex]);
}
else
{
categoryDirectChild.PlaceBehind(this[insertIndex]);
}
}
lastInsertedElement = firstChild;
}
// Place every subsequent row after that use PlaceInFront(), this prevents weird re-ordering issues as long as we can get the first index right
else
{
var indexOfFirstChild = this.IndexOf(lastInsertedElement);
categoryDirectChild.PlaceInFront(this[indexOfFirstChild]);
lastInsertedElement = categoryDirectChild;
}
if (insertIndex > childCount - 1 || listEndInsertion)
insertIndex = -1;
var moveShaderInputAction = new MoveShaderInputAction();
moveShaderInputAction.associatedCategoryGuid = viewModel.associatedCategoryGuid;
moveShaderInputAction.shaderInputReference = shaderInput;
moveShaderInputAction.newIndexValue = insertIndex;
m_ViewModel.requestModelChangeAction(moveShaderInputAction);
// Make sure to remove the element from the selection so it doesn't get re-handled by the blackboard as well, leads to duplicates
selection.Remove(blackboardField);
if (insertIndex > indexOfDraggedElement)
continue;
// If adding to the end of the list, we no longer need to increment the index
if (insertIndex != -1)
insertIndex++;
}
}
/* Handles moving elements from one category to another (including between different graph windows) */
// Handles case of inserting after item in list
if (!adjustedInsertIndex)
{
if (m_DroppedOnBottomEdge)
{
insertIndex++;
}
// Only ever do this for the first item
else if (m_DroppedOnTopEdge && insertIndex == 0)
{
insertIndex = Mathf.Clamp(insertIndex - 1, 0, childCount - 1);
}
}
else if (lastInsertedElement != null)
{
insertIndex = this.IndexOf(lastInsertedElement) + 1;
}
foreach (var categoryToChildrenTuple in draggedElements)
{
var childList = categoryToChildrenTuple.Value;
foreach (var draggedElement in childList)
{
var blackboardField = draggedElement.Q<SGBlackboardField>();
ShaderInput shaderInput = null;
if (blackboardField != null)
shaderInput = blackboardField.controller.Model;
if (shaderInput == null)
continue;
// If the blackboard field is contained by this category its already been handled above, skip
if (this.Contains(blackboardField))
continue;
var addItemToCategoryAction = new AddItemToCategoryAction();
addItemToCategoryAction.categoryGuid = viewModel.associatedCategoryGuid;
addItemToCategoryAction.addActionSource = AddItemToCategoryAction.AddActionSource.DragDrop;
addItemToCategoryAction.itemToAdd = shaderInput;
// If adding to end of list, make the insert index -1 to ensure op goes through as expected
if (insertIndex > childCount - 1)
insertIndex = -1;
addItemToCategoryAction.indexToAddItemAt = insertIndex;
m_ViewModel.requestModelChangeAction(addItemToCategoryAction);
// Make sure to remove the element from the selection so it doesn't get re-handled by the blackboard as well, leads to duplicates
selection.Remove(blackboardField);
// If adding to the end of the list, we no longer need to increment the index
if (insertIndex != -1)
insertIndex++;
}
}
SetDragIndicatorVisible(false);
}
void OnDragLeaveEvent(DragLeaveEvent evt)
{
SetDragIndicatorVisible(false);
}
internal void OnDragActionCanceled()
{
SetDragIndicatorVisible(false);
m_IsDragInProgress = false;
}
public override void Select(VisualElement selectionContainer, bool additive)
{
// Don't add the un-named/default category to graph selections
if (controller.Model.IsNamedCategory())
{
base.Select(selectionContainer, additive);
}
}
public override void OnSelected()
{
AddToClassList("selected");
}
public override void OnUnselected()
{
RemoveFromClassList("selected");
}
public void AddToSelection(ISelectable selectable)
{
// Don't add the un-named/default category to graph selections,
if (controller.Model.IsNamedCategory() == false && selectable == this)
return;
// Don't add to selection if a rename op is in progress
if (m_RenameInProgress)
{
RemoveFromSelection(this);
return;
}
var materialGraphView = m_ViewModel.parentView.GetFirstAncestorOfType<MaterialGraphView>();
materialGraphView?.AddToSelection(selectable);
}
public void RemoveFromSelection(ISelectable selectable)
{
var materialGraphView = m_ViewModel.parentView.GetFirstAncestorOfType<MaterialGraphView>();
// If we're de-selecting the category itself
if (selectable == this)
{
materialGraphView?.RemoveFromSelection(selectable);
// Also deselect the child elements within this category (the field views)
var fieldViews = this.Query<SGBlackboardField>();
foreach (var child in fieldViews.ToList())
{
materialGraphView?.RemoveFromSelection(child);
}
}
// If a category is unselected, only then can the children beneath it be deselected
else if (selection.Contains(this) == false)
{
materialGraphView?.RemoveFromSelection(selectable);
}
}
public void ClearSelection()
{
RemoveFromClassList("selected");
var materialGraphView = m_ViewModel.parentView.GetFirstAncestorOfType<MaterialGraphView>();
materialGraphView?.ClearSelection();
}
public List<ISelectable> selection
{
get
{
var selectionProvider = m_ViewModel.parentView.GetFirstAncestorOfType<ISelectionProvider>();
if (selectionProvider?.GetSelection != null)
return selectionProvider.GetSelection;
return new List<ISelectable>();
}
}
void RequestCategoryDelete()
{
var materialGraphView = blackboard.ParentView as MaterialGraphView;
materialGraphView?.deleteSelection?.Invoke("Delete", GraphView.AskUser.DontAskUser);
}
void AddContextMenuOptions(ContextualMenuPopulateEvent evt)
{
evt.menu.AppendAction("Delete", evt => RequestCategoryDelete());
evt.menu.AppendAction("Rename", (a) => OpenTextEditor(), DropdownMenuAction.AlwaysEnabled);
// Don't allow the default/un-named category to have right-click menu options
if (controller.Model.IsNamedCategory())
{
evt.menu.AppendAction("Delete", evt => RequestCategoryDelete());
}
}
public int CompareTo(SGBlackboardCategory other)
{
if (other == null)
return 1;
var thisBlackboard = this.blackboard;
var otherBlackboard = other.blackboard;
return thisBlackboard.IndexOf(this).CompareTo(otherBlackboard.IndexOf(other));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5e118fcc48ee4d78b7ea01a1d8011b1b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,387 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.Graphing;
using UnityEditor.Experimental.GraphView;
using UnityEditor.ShaderGraph.Drawing.Controls;
using UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine.Assertions;
using ContextualMenuManipulator = UnityEngine.UIElements.ContextualMenuManipulator;
using GraphDataStore = UnityEditor.ShaderGraph.DataStore<UnityEditor.ShaderGraph.GraphData>;
namespace UnityEditor.ShaderGraph.Drawing
{
class SGBlackboardField : GraphElement, IInspectable, ISGControlledElement<ShaderInputViewController>
{
static readonly Texture2D k_ExposedIcon = Resources.Load<Texture2D>("GraphView/Nodes/BlackboardFieldExposed");
static readonly string k_UxmlTemplatePath = "UXML/Blackboard/SGBlackboardField";
static readonly string k_StyleSheetPath = "Styles/SGBlackboard";
ShaderInputViewModel m_ViewModel;
ShaderInputViewModel ViewModel
{
get => m_ViewModel;
set => m_ViewModel = value;
}
VisualElement m_ContentItem;
Pill m_Pill;
Label m_TypeLabel;
TextField m_TextField;
internal TextField textField => m_TextField;
Action m_ResetReferenceNameTrigger;
List<Node> m_SelectedNodes = new List<Node>();
public string text
{
get { return m_Pill.text; }
set { m_Pill.text = value; }
}
public string typeText
{
get { return m_TypeLabel.text; }
set { m_TypeLabel.text = value; }
}
public Texture icon
{
get { return m_Pill.icon; }
set { m_Pill.icon = value; }
}
public bool highlighted
{
get { return m_Pill.highlighted; }
set { m_Pill.highlighted = value; }
}
internal SGBlackboardField(ShaderInputViewModel viewModel)
{
ViewModel = viewModel;
// Store ShaderInput in userData object
userData = ViewModel.model;
if (userData == null)
{
AssertHelpers.Fail("Could not initialize blackboard field as shader input was null.");
return;
}
// Store the Model guid as viewDataKey as that is persistent
viewDataKey = ViewModel.model.guid.ToString();
var visualTreeAsset = Resources.Load<VisualTreeAsset>(k_UxmlTemplatePath);
Assert.IsNotNull(visualTreeAsset);
VisualElement mainContainer = visualTreeAsset.Instantiate();
var styleSheet = Resources.Load<StyleSheet>(k_StyleSheetPath);
Assert.IsNotNull(styleSheet);
styleSheets.Add(styleSheet);
mainContainer.AddToClassList("mainContainer");
mainContainer.pickingMode = PickingMode.Ignore;
m_ContentItem = mainContainer.Q("contentItem");
m_Pill = mainContainer.Q<Pill>("pill");
m_TypeLabel = mainContainer.Q<Label>("typeLabel");
m_TextField = mainContainer.Q<TextField>("textField");
m_TextField.style.display = DisplayStyle.None;
// Update the Pill text if shader input name is changed
// we handle this in controller if we change it through SGBlackboardField, but its possible to change through PropertyNodeView as well
shaderInput.displayNameUpdateTrigger += newDisplayName => text = newDisplayName;
// Handles the upgrade fix for the old color property deprecation
if (shaderInput is AbstractShaderProperty property)
{
property.onAfterVersionChange += () =>
{
this.typeText = property.GetPropertyTypeString();
this.m_InspectorUpdateDelegate();
};
}
Add(mainContainer);
RegisterCallback<MouseDownEvent>(OnMouseDownEvent);
capabilities |= Capabilities.Selectable | Capabilities.Droppable | Capabilities.Deletable | Capabilities.Renamable;
ClearClassList();
AddToClassList("blackboardField");
this.name = "SGBlackboardField";
UpdateFromViewModel();
// add the right click context menu
IManipulator contextMenuManipulator = new ContextualMenuManipulator(AddContextMenuOptions);
this.AddManipulator(contextMenuManipulator);
this.AddManipulator(new SelectionDropper());
this.AddManipulator(new ContextualMenuManipulator(BuildFieldContextualMenu));
// When a display name is changed through the BlackboardPill, bind this callback to handle it with appropriate change action
var textInputElement = m_TextField.Q(TextField.textInputUssName);
textInputElement.RegisterCallback<FocusOutEvent>(e => { OnEditTextFinished(); });
ShaderGraphPreferences.onAllowDeprecatedChanged += UpdateTypeText;
RegisterCallback<MouseEnterEvent>(evt => OnMouseHover(evt, ViewModel.model));
RegisterCallback<MouseLeaveEvent>(evt => OnMouseHover(evt, ViewModel.model));
RegisterCallback<DragUpdatedEvent>(OnDragUpdatedEvent);
var blackboard = ViewModel.parentView.GetFirstAncestorOfType<SGBlackboard>();
if (blackboard != null)
{
// These callbacks are used for the property dragging scroll behavior
RegisterCallback<DragEnterEvent>(blackboard.OnDragEnterEvent);
RegisterCallback<DragExitedEvent>(blackboard.OnDragExitedEvent);
// These callbacks are used for the property dragging scroll behavior
RegisterCallback<DragEnterEvent>(blackboard.OnDragEnterEvent);
RegisterCallback<DragExitedEvent>(blackboard.OnDragExitedEvent);
}
}
~SGBlackboardField()
{
ShaderGraphPreferences.onAllowDeprecatedChanged -= UpdateTypeText;
}
void AddContextMenuOptions(ContextualMenuPopulateEvent evt)
{
// Checks if the reference name has been overridden and appends menu action to reset it, if so
if (shaderInput.isRenamable &&
!string.IsNullOrEmpty(shaderInput.overrideReferenceName))
{
evt.menu.AppendAction(
"Reset Reference",
e =>
{
var resetReferenceNameAction = new ResetReferenceNameAction();
resetReferenceNameAction.shaderInputReference = shaderInput;
ViewModel.requestModelChangeAction(resetReferenceNameAction);
m_ResetReferenceNameTrigger();
},
DropdownMenuAction.AlwaysEnabled);
}
if (shaderInput is ColorShaderProperty colorProp)
{
PropertyNodeView.AddMainColorMenuOptions(evt, colorProp, controller.graphData, m_InspectorUpdateDelegate);
}
if (shaderInput is Texture2DShaderProperty texProp)
{
PropertyNodeView.AddMainTextureMenuOptions(evt, texProp, controller.graphData, m_InspectorUpdateDelegate);
}
}
internal void UpdateFromViewModel()
{
this.text = ViewModel.inputName;
this.icon = ViewModel.isInputExposed ? k_ExposedIcon : null;
this.typeText = ViewModel.inputTypeName;
}
ShaderInputViewController m_Controller;
// --- Begin ISGControlledElement implementation
public void OnControllerChanged(ref SGControllerChangedEvent e)
{
}
public void OnControllerEvent(SGControllerEvent e)
{
}
public ShaderInputViewController controller
{
get => m_Controller;
set
{
if (m_Controller != value)
{
if (m_Controller != null)
{
m_Controller.UnregisterHandler(this);
}
m_Controller = value;
if (m_Controller != null)
{
m_Controller.RegisterHandler(this);
}
}
}
}
SGController ISGControlledElement.controller => m_Controller;
// --- ISGControlledElement implementation
[Inspectable("Shader Input", null)]
public ShaderInput shaderInput => ViewModel.model;
public string inspectorTitle => ViewModel.inputName + " " + ViewModel.inputTypeName;
public object GetObjectToInspect()
{
return shaderInput;
}
Action m_InspectorUpdateDelegate;
public void SupplyDataToPropertyDrawer(IPropertyDrawer propertyDrawer, Action inspectorUpdateDelegate)
{
if (propertyDrawer is ShaderInputPropertyDrawer shaderInputPropertyDrawer)
{
// We currently need to do a halfway measure between the old way of handling stuff for property drawers (how FieldView and NodeView handle it)
// and how we want to handle it with the new style of controllers and views. Ideally we'd just hand the property drawer a view model and thats it.
// We've maintained all the old callbacks as they are in the PropertyDrawer to reduce possible halo changes and support PropertyNodeView functionality
// Instead we supply different underlying methods for the callbacks in the new SGBlackboardField,
// that way both code paths should work until we can refactor PropertyNodeView
shaderInputPropertyDrawer.GetViewModel(
ViewModel,
controller.graphData,
((triggerInspectorUpdate, modificationScope) =>
{
controller.DirtyNodes(modificationScope);
if (triggerInspectorUpdate)
inspectorUpdateDelegate();
}));
m_ResetReferenceNameTrigger = shaderInputPropertyDrawer.ResetReferenceName;
m_InspectorUpdateDelegate = inspectorUpdateDelegate;
}
}
void OnMouseDownEvent(MouseDownEvent e)
{
if ((e.clickCount == 2) && e.button == (int)MouseButton.LeftMouse && IsRenamable())
{
OpenTextEditor();
e.PreventDefault();
}
else
{
e.StopPropagation();
}
}
void OnDragUpdatedEvent(DragUpdatedEvent evt)
{
if (m_SelectedNodes.Any())
{
foreach (var node in m_SelectedNodes)
{
node.RemoveFromClassList("hovered");
}
m_SelectedNodes.Clear();
}
}
// TODO: Move to controller? Feels weird for this to be directly communicating with PropertyNodes etc.
// Better way would be to send event to controller that notified of hover enter/exit and have other controllers be sent those events in turn
void OnMouseHover(EventBase evt, ShaderInput input)
{
var graphView = ViewModel.parentView.GetFirstAncestorOfType<MaterialGraphView>();
if (evt.eventTypeId == MouseEnterEvent.TypeId())
{
foreach (var node in graphView.nodes.ToList())
{
if (input is AbstractShaderProperty property)
{
if (node.userData is PropertyNode propertyNode)
{
if (propertyNode.property == input)
{
m_SelectedNodes.Add(node);
node.AddToClassList("hovered");
}
}
}
else if (input is ShaderKeyword keyword)
{
if (node.userData is KeywordNode keywordNode)
{
if (keywordNode.keyword == input)
{
m_SelectedNodes.Add(node);
node.AddToClassList("hovered");
}
}
}
else if (input is ShaderDropdown dropdown)
{
if (node.userData is DropdownNode dropdownNode)
{
if (dropdownNode.dropdown == input)
{
m_SelectedNodes.Add(node);
node.AddToClassList("hovered");
}
}
}
}
}
else if (evt.eventTypeId == MouseLeaveEvent.TypeId() && m_SelectedNodes.Any())
{
foreach (var node in m_SelectedNodes)
{
node.RemoveFromClassList("hovered");
}
m_SelectedNodes.Clear();
}
}
void UpdateTypeText()
{
if (shaderInput is AbstractShaderProperty asp)
{
typeText = asp.GetPropertyTypeString();
}
}
internal void OpenTextEditor()
{
m_TextField.SetValueWithoutNotify(text);
m_TextField.style.display = DisplayStyle.Flex;
m_ContentItem.visible = false;
m_TextField.Q(TextField.textInputUssName).Focus();
m_TextField.SelectAll();
}
void OnEditTextFinished()
{
m_ContentItem.visible = true;
m_TextField.style.display = DisplayStyle.None;
if (text != m_TextField.text && String.IsNullOrWhiteSpace(m_TextField.text) == false && String.IsNullOrEmpty(m_TextField.text) == false)
{
var changeDisplayNameAction = new ChangeDisplayNameAction();
changeDisplayNameAction.shaderInputReference = shaderInput;
changeDisplayNameAction.newDisplayNameValue = m_TextField.text;
ViewModel.requestModelChangeAction(changeDisplayNameAction);
m_InspectorUpdateDelegate?.Invoke();
}
else
{
// Reset text field to original name
m_TextField.value = text;
}
}
protected virtual void BuildFieldContextualMenu(ContextualMenuPopulateEvent evt)
{
evt.menu.AppendAction("Rename", (a) => OpenTextEditor(), DropdownMenuAction.AlwaysEnabled);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 04f0848d35cb46cabeec016d7ccd5169
timeCreated: 1611267123

View file

@ -0,0 +1,78 @@
using System;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing
{
class SGBlackboardRow : VisualElement
{
static readonly string k_UxmlTemplatePath = "UXML/Blackboard/SGBlackboardRow";
static readonly string k_StyleSheetPath = "Styles/SGBlackboard";
VisualElement m_Root;
Button m_ExpandButton;
VisualElement m_ItemContainer;
VisualElement m_PropertyViewContainer;
bool m_Expanded = true;
public bool expanded
{
get { return m_Expanded; }
set
{
if (m_Expanded == value)
{
return;
}
m_Expanded = value;
if (m_Expanded)
{
m_Root.Add(m_PropertyViewContainer);
AddToClassList("expanded");
}
else
{
m_Root.Remove(m_PropertyViewContainer);
RemoveFromClassList("expanded");
}
}
}
public SGBlackboardRow(VisualElement item, VisualElement propertyView)
{
var stylesheet = Resources.Load(k_StyleSheetPath) as StyleSheet;
Assert.IsNotNull(stylesheet);
styleSheets.Add(stylesheet);
var uxmlTemplate = Resources.Load(k_UxmlTemplatePath) as VisualTreeAsset;
Assert.IsNotNull(uxmlTemplate);
VisualElement mainContainer = null;
mainContainer = uxmlTemplate.Instantiate();
Assert.IsNotNull(mainContainer);
mainContainer.AddToClassList("mainContainer");
m_Root = mainContainer.Q("root");
m_ItemContainer = mainContainer.Q("itemContainer");
m_PropertyViewContainer = mainContainer.Q("propertyViewContainer");
m_ExpandButton = mainContainer.Q<Button>("expandButton");
m_ExpandButton.clickable.clicked += () => expanded = !expanded;
m_ExpandButton.RemoveFromHierarchy();
Add(mainContainer);
ClearClassList();
AddToClassList("blackboardRow");
name = "SGBlackboardRow";
m_ItemContainer.Add(item);
m_PropertyViewContainer.Add(propertyView);
expanded = false;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7481cf2e22074dbdb915ebd086bd0652
timeCreated: 1611611278

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9ff4538fd9b634ffd8b591bd96663d06
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,26 @@
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Colors
{
class CategoryColors : ColorProviderFromStyleSheet
{
public override string GetTitle() => "Category";
public override bool AllowCustom() => false;
public override bool ClearOnDirty() => false;
protected override bool GetClassFromNode(AbstractMaterialNode node, out string ussClass)
{
ussClass = string.Empty;
if (!(node.GetType().GetCustomAttributes(typeof(TitleAttribute), false).FirstOrDefault() is TitleAttribute title))
return false;
ussClass = title.title[0];
return !string.IsNullOrEmpty(ussClass);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 93e3298dd3de748f18fe0a2c486c15f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace UnityEditor.ShaderGraph.Drawing.Colors
{
// Use this to set colors on your node titles.
// There are 2 methods of setting colors - direct Color objects via code (such as data saved in the node itself),
// or setting classes on a VisualElement, allowing the colors themselves to be defined in USS. See notes on
// IColorProvider for how to use these different methods.
class ColorManager
{
static string DefaultProvider = NoColors.Title;
List<IColorProvider> m_Providers;
int m_ActiveIndex = 0;
public int activeIndex
{
get => m_ActiveIndex;
private set
{
if (!IsValidIndex(value))
return;
m_ActiveIndex = value;
}
}
public ColorManager(string activeProvider)
{
m_Providers = new List<IColorProvider>();
if (string.IsNullOrEmpty(activeProvider))
activeProvider = DefaultProvider;
foreach (var colorType in TypeCache.GetTypesDerivedFrom<IColorProvider>().Where(t => !t.IsAbstract))
{
var provider = (IColorProvider)Activator.CreateInstance(colorType);
m_Providers.Add(provider);
}
m_Providers.Sort((p1, p2) => string.Compare(p1.GetTitle(), p2.GetTitle(), StringComparison.InvariantCulture));
activeIndex = m_Providers.FindIndex(provider => provider.GetTitle() == activeProvider);
}
public void SetNodesDirty(IEnumerable<IShaderNodeView> nodeViews)
{
if (activeProvider.ClearOnDirty())
{
foreach (var view in nodeViews)
{
activeProvider.ClearColor(view);
}
}
}
public void SetActiveProvider(int newIndex, IEnumerable<IShaderNodeView> nodeViews)
{
if (newIndex == activeIndex || !IsValidIndex(newIndex))
return;
var oldProvider = activeProvider;
activeIndex = newIndex;
foreach (var view in nodeViews)
{
oldProvider.ClearColor(view);
activeProvider.ApplyColor(view);
}
}
public void UpdateNodeViews(IEnumerable<IShaderNodeView> nodeViews)
{
foreach (var view in nodeViews)
{
UpdateNodeView(view);
}
}
public void UpdateNodeView(IShaderNodeView nodeView)
{
activeProvider.ApplyColor(nodeView);
}
public IEnumerable<string> providerNames => m_Providers.Select(p => p.GetTitle());
public string activeProviderName => activeProvider.GetTitle();
public bool activeSupportsCustom => activeProvider.AllowCustom();
IColorProvider activeProvider => m_Providers[activeIndex];
bool IsValidIndex(int index) => index >= 0 && index < m_Providers.Count;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 739266dbe5679492f90aba99b9265731
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using UnityEditor.Graphing;
using UnityEngine;
namespace UnityEditor.ShaderGraph.Drawing.Colors
{
[Serializable]
class SerializableUserColor
{
public string Key = String.Empty;
public Color Value = Color.black;
public SerializableUserColor(KeyValuePair<string, Color> pair) { Key = pair.Key; Value = pair.Value; }
// Empty constructor required by serialization system
public SerializableUserColor() { }
}
[Serializable]
public class CustomColorData : ISerializationCallbackReceiver
{
Dictionary<string, Color> m_CustomColors = new Dictionary<string, Color>();
[SerializeField]
List<SerializationHelper.JSONSerializedElement> m_SerializableColors = new List<SerializationHelper.JSONSerializedElement>();
public bool TryGetColor(string provider, out Color color)
{
return m_CustomColors.TryGetValue(provider, out color);
}
public void Set(string provider, Color color)
{
m_CustomColors[provider] = color;
}
public void Remove(string provider)
{
m_CustomColors.Remove(provider);
}
public void OnBeforeSerialize()
{
m_SerializableColors.Clear();
foreach (var customColorKvp in m_CustomColors)
{
m_SerializableColors.Add(SerializationHelper.Serialize(new SerializableUserColor(customColorKvp)));
}
}
public void OnAfterDeserialize()
{
List<SerializableUserColor> colors = SerializationHelper.Deserialize<SerializableUserColor>(m_SerializableColors, null);
foreach (var colorPair in colors)
{
m_CustomColors.Add(colorPair.Key, colorPair.Value);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 77e837a1c5a3a4f3f873a99e6287a220
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,72 @@
using UnityEngine;
namespace UnityEditor.ShaderGraph.Drawing.Colors
{
// Defines how the ColorManager interacts with various providers
interface IColorProvider
{
string GetTitle();
bool AllowCustom();
bool ClearOnDirty();
void ApplyColor(IShaderNodeView nodeView);
void ClearColor(IShaderNodeView nodeView);
}
internal abstract class ColorProviderFromCode : IColorProvider
{
protected abstract bool GetColorFromNode(AbstractMaterialNode node, out Color color);
public abstract string GetTitle();
public abstract bool AllowCustom();
public abstract bool ClearOnDirty();
public virtual void ApplyColor(IShaderNodeView nodeView)
{
if (GetColorFromNode(nodeView.node, out var color))
{
nodeView.SetColor(color);
}
else
{
nodeView.ResetColor();
}
}
public virtual void ClearColor(IShaderNodeView nodeView)
{
nodeView.ResetColor();
}
}
internal abstract class ColorProviderFromStyleSheet : IColorProvider
{
protected abstract bool GetClassFromNode(AbstractMaterialNode node, out string ussClass);
public abstract string GetTitle();
public abstract bool AllowCustom();
public abstract bool ClearOnDirty();
public virtual void ApplyColor(IShaderNodeView nodeView)
{
if (GetClassFromNode(nodeView.node, out var ussClass))
{
nodeView.colorElement.AddToClassList(ussClass);
}
}
public virtual void ClearColor(IShaderNodeView nodeView)
{
if (GetClassFromNode(nodeView.node, out var ussClass))
{
nodeView.colorElement.RemoveFromClassList(ussClass);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8fede14600fd343d592ea0747a5fd164
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,22 @@
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Colors
{
internal class NoColors : IColorProvider
{
public const string Title = "<None>";
public string GetTitle() => Title;
public bool AllowCustom() => false;
public bool ClearOnDirty() => false;
public void ApplyColor(IShaderNodeView nodeView)
{
}
public void ClearColor(IShaderNodeView nodeView)
{
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a22ff676828b743cc8de233f626453ca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,32 @@
using System;
using System.Linq;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Colors
{
class PrecisionColors : ColorProviderFromStyleSheet
{
public override string GetTitle() => "Precision";
public override bool AllowCustom() => false;
public override bool ClearOnDirty() => true;
protected override bool GetClassFromNode(AbstractMaterialNode node, out string ussClass)
{
var graphPrecision = node.graphPrecision.GraphFallback(node.owner.graphDefaultPrecision);
ussClass = graphPrecision.ToString();
return !string.IsNullOrEmpty(ussClass);
}
public override void ClearColor(IShaderNodeView nodeView)
{
foreach (var type in GraphPrecision.GetValues(typeof(GraphPrecision)))
{
nodeView.colorElement.RemoveFromClassList(type.ToString());
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0656a3ae06559c940a3ba33cef303f99
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,20 @@
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Colors
{
class UserColors : ColorProviderFromCode
{
const string m_Title = "User Defined";
public override string GetTitle() => m_Title;
public override bool AllowCustom() => true;
public override bool ClearOnDirty() => false;
protected override bool GetColorFromNode(AbstractMaterialNode node, out Color color)
{
color = Color.black;
return node.TryGetColor(m_Title, ref color);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bca71e015fbd848c39feff083689afca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b1b1a4fb9c392ef49acffada0059f01e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 29309be0e1004fad967256187775c528
timeCreated: 1507642261

View file

@ -0,0 +1,47 @@
using System;
using System.Reflection;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class ButtonControlAttribute : Attribute, IControlAttribute
{
public ButtonControlAttribute()
{
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new ButtonControlView(node, propertyInfo);
}
}
[Serializable]
struct ButtonConfig
{
public string text;
public Action action;
}
class ButtonControlView : VisualElement
{
public ButtonControlView(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
AbstractMaterialNode m_Node;
m_Node = node;
Type type = propertyInfo.PropertyType;
if (type != typeof(ButtonConfig))
{
throw new ArgumentException("Property must be a ButtonConfig.", "propertyInfo");
}
var value = (ButtonConfig)propertyInfo.GetValue(m_Node, null);
Add(new Button(value.action) { text = value.text });
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a7a22fecc3eba0844810adaf43e45f0f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEditor.Graphing;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class ChannelEnumControlAttribute : Attribute, IControlAttribute
{
string m_Label;
int m_SlotId;
public ChannelEnumControlAttribute(string label = null, int slotId = 0)
{
m_Label = label;
m_SlotId = slotId;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new ChannelEnumControlView(m_Label, m_SlotId, node, propertyInfo);
}
}
class ChannelEnumControlView : VisualElement, AbstractMaterialNodeModificationListener
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
int m_SlotId;
PopupField<string> m_PopupField;
string[] m_ValueNames;
int m_PreviousChannelCount = -1;
public ChannelEnumControlView(string label, int slotId, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/ChannelEnumControlView"));
m_Node = node;
m_PropertyInfo = propertyInfo;
m_SlotId = slotId;
if (!propertyInfo.PropertyType.IsEnum)
throw new ArgumentException("Property must be an enum.", "propertyInfo");
Add(new Label(label ?? ObjectNames.NicifyVariableName(propertyInfo.Name)));
var value = (Enum)m_PropertyInfo.GetValue(m_Node, null);
m_ValueNames = Enum.GetNames(value.GetType());
CreatePopup();
}
void OnValueChanged(ChangeEvent<string> evt)
{
var index = m_PopupField.index;
var value = (int)m_PropertyInfo.GetValue(m_Node, null);
if (!index.Equals(value))
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Change " + m_Node.name);
m_PropertyInfo.SetValue(m_Node, index, null);
}
CreatePopup();
}
public void OnNodeModified(ModificationScope scope)
{
if (scope == ModificationScope.Node)
{
CreatePopup();
m_PopupField.MarkDirtyRepaint();
}
}
void CreatePopup()
{
int channelCount = SlotValueHelper.GetChannelCount(m_Node.FindSlot<MaterialSlot>(m_SlotId).concreteValueType);
if (m_PopupField != null)
{
if (channelCount == m_PreviousChannelCount)
return;
Remove(m_PopupField);
}
m_PreviousChannelCount = channelCount;
List<string> popupEntries = new List<string>();
for (int i = 0; i < channelCount; i++)
popupEntries.Add(m_ValueNames[i]);
var value = (int)m_PropertyInfo.GetValue(m_Node, null);
if (value >= channelCount)
value = 0;
m_PopupField = new PopupField<string>(popupEntries, value);
m_PopupField.RegisterValueChangedCallback(OnValueChanged);
Add(m_PopupField);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 74fdde12d8253bd4c874acc555be0585
timeCreated: 1507817885

View file

@ -0,0 +1,83 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor.Graphing;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class ChannelEnumMaskControlAttribute : Attribute, IControlAttribute
{
string m_Label;
int m_SlotId;
public ChannelEnumMaskControlAttribute(string label = null, int slotId = 0)
{
m_Label = label;
m_SlotId = slotId;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new ChannelEnumMaskControlView(m_Label, m_SlotId, node, propertyInfo);
}
}
class ChannelEnumMaskControlView : VisualElement, AbstractMaterialNodeModificationListener
{
string m_Label;
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
IMGUIContainer m_Container;
int m_SlotId;
public ChannelEnumMaskControlView(string label, int slotId, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/ChannelEnumMaskControlView"));
m_Node = node;
m_PropertyInfo = propertyInfo;
m_SlotId = slotId;
//if (!propertyInfo.PropertyType.IsEnum)
//throw new ArgumentException("Property must be an enum.", "propertyInfo");
m_Label = label;
m_Container = new IMGUIContainer(OnGUIHandler);
Add(m_Container);
}
void OnGUIHandler()
{
GUILayout.BeginHorizontal();
GUILayout.Label(m_Label);
UpdatePopup();
GUILayout.EndHorizontal();
}
public void OnNodeModified(ModificationScope scope)
{
if (scope == ModificationScope.Graph)
m_Container.MarkDirtyRepaint();
}
private void UpdatePopup()
{
var value = (int)m_PropertyInfo.GetValue(m_Node, null);
using (var changeCheckScope = new EditorGUI.ChangeCheckScope())
{
int channelCount = SlotValueHelper.GetChannelCount(m_Node.FindSlot<MaterialSlot>(m_SlotId).concreteValueType);
string[] enumEntryNames = Enum.GetNames(typeof(TextureChannel));
string[] popupEntries = new string[channelCount];
for (int i = 0; i < popupEntries.Length; i++)
popupEntries[i] = enumEntryNames[i];
value = EditorGUILayout.MaskField("", value, popupEntries, GUILayout.Width(80f));
if (changeCheckScope.changed)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Change " + m_Node.name);
m_PropertyInfo.SetValue(m_Node, value, null);
}
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c32d860c6f767f14fa889dffac527bc5
timeCreated: 1507817885

View file

@ -0,0 +1,214 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class ChannelMixerControlAttribute : Attribute, IControlAttribute
{
string m_Label;
float m_Minimum;
float m_Maximum;
public ChannelMixerControlAttribute(string label = null, float minimum = -2f, float maximum = 2f)
{
m_Label = label;
m_Minimum = minimum;
m_Maximum = maximum;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new ChannelMixerControlView(m_Label, m_Minimum, m_Maximum, node, propertyInfo);
}
}
class ChannelMixerControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
ChannelMixerNode.ChannelMixer m_ChannelMixer;
int m_OutChannel;
Slider m_RedSlider;
Slider m_GreenSlider;
Slider m_BlueSlider;
FloatField m_RedInputField;
FloatField m_GreenInputField;
FloatField m_BlueInputField;
float m_Minimum;
float m_Maximum;
bool m_Initialized;
public ChannelMixerControlView(string label, float minimum, float maximum, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
m_Node = node;
m_PropertyInfo = propertyInfo;
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/ChannelMixerControlView"));
m_ChannelMixer = (ChannelMixerNode.ChannelMixer)m_PropertyInfo.GetValue(m_Node, null);
m_OutChannel = 0;
m_Minimum = minimum;
m_Maximum = maximum;
if (propertyInfo.PropertyType != typeof(ChannelMixerNode.ChannelMixer))
throw new ArgumentException("Property must be of type ChannelMixer.", "propertyInfo");
label = label ?? ObjectNames.NicifyVariableName(propertyInfo.Name);
if (!string.IsNullOrEmpty(label))
Add(new Label(label));
var buttonPanel = new VisualElement { name = "buttonPanel" };
Action changedOutputRed = () => OnClickButton(0);
var outputButtonRed = new Button(changedOutputRed);
outputButtonRed.Add(new Label("R"));
buttonPanel.Add(outputButtonRed);
Action changedOutputGreen = () => OnClickButton(1);
var outputButtonGreen = new Button(changedOutputGreen);
outputButtonGreen.Add(new Label("G"));
buttonPanel.Add(outputButtonGreen);
Action changedOutputBlue = () => OnClickButton(2);
var outputButtonBlue = new Button(changedOutputBlue);
outputButtonBlue.Add(new Label("B"));
buttonPanel.Add(outputButtonBlue);
Add(buttonPanel);
var redSliderPanel = new VisualElement { name = "sliderPanel" };
redSliderPanel.Add(new Label("R"));
Action<float> changedRedIn = (s) => { OnChangeSlider(s, 0); };
m_RedSlider = new Slider(m_Minimum, m_Maximum);
m_RedSlider.RegisterValueChangedCallback((evt) => OnChangeSlider(evt.newValue, 0));
redSliderPanel.Add(m_RedSlider);
m_RedInputField = new FloatField { value = m_ChannelMixer.outRed.x };
m_RedInputField.RegisterCallback<ChangeEvent<double>, int>(OnChangeInputField, 0);
redSliderPanel.Add(m_RedInputField);
Add(redSliderPanel);
var greenSliderPanel = new VisualElement { name = "sliderPanel" };
greenSliderPanel.Add(new Label("G"));
m_GreenSlider = new Slider(m_Minimum, m_Maximum);
m_GreenSlider.RegisterValueChangedCallback((evt) => OnChangeSlider(evt.newValue, 1));
greenSliderPanel.Add(m_GreenSlider);
m_GreenInputField = new FloatField { value = m_ChannelMixer.outRed.y };
m_GreenInputField.RegisterCallback<ChangeEvent<double>, int>(OnChangeInputField, 1);
greenSliderPanel.Add(m_GreenInputField);
Add(greenSliderPanel);
var blueSliderPanel = new VisualElement { name = "sliderPanel" };
blueSliderPanel.Add(new Label("B"));
m_BlueSlider = new Slider(m_Minimum, m_Maximum);
m_BlueSlider.RegisterValueChangedCallback((evt) => OnChangeSlider(evt.newValue, 2));
blueSliderPanel.Add(m_BlueSlider);
m_BlueInputField = new FloatField { value = m_ChannelMixer.outRed.z };
m_BlueInputField.RegisterCallback<ChangeEvent<double>, int>(OnChangeInputField, 2);
blueSliderPanel.Add(m_BlueInputField);
Add(blueSliderPanel);
m_Initialized = true;
ResetSliders();
}
void ResetSliders()
{
Vector3 outputChannel = GetOutputChannel();
m_RedSlider.value = outputChannel[0];
m_GreenSlider.value = outputChannel[1];
m_BlueSlider.value = outputChannel[2];
}
void OnChangeSlider(float value, int inChannel)
{
if (!m_Initialized)
return;
m_Node.owner.owner.RegisterCompleteObjectUndo("Slider Change");
switch (m_OutChannel)
{
case 1:
m_ChannelMixer.outGreen[inChannel] = value;
break;
case 2:
m_ChannelMixer.outBlue[inChannel] = value;
break;
default:
m_ChannelMixer.outRed[inChannel] = value;
break;
}
switch (inChannel)
{
case 1:
m_GreenInputField.value = value;
break;
case 2:
m_BlueInputField.value = value;
break;
default:
m_RedInputField.value = value;
break;
}
m_PropertyInfo.SetValue(m_Node, m_ChannelMixer, null);
}
void OnChangeInputField(ChangeEvent<double> evt, int inChannel)
{
if (!m_Initialized)
return;
var value = Mathf.Max(Mathf.Min((float)evt.newValue, m_Maximum), m_Minimum);
m_Node.owner.owner.RegisterCompleteObjectUndo("Input Field Change");
switch (m_OutChannel)
{
case 1:
m_ChannelMixer.outGreen[inChannel] = value;
break;
case 2:
m_ChannelMixer.outBlue[inChannel] = value;
break;
default:
m_ChannelMixer.outRed[inChannel] = value;
break;
}
switch (inChannel)
{
case 1:
m_GreenSlider.value = value;
break;
case 2:
m_BlueSlider.value = value;
break;
default:
m_RedSlider.value = value;
break;
}
m_PropertyInfo.SetValue(m_Node, m_ChannelMixer, null);
this.MarkDirtyRepaint();
}
void OnClickButton(int outChannel)
{
m_OutChannel = outChannel;
ResetSliders();
}
Vector3 GetOutputChannel()
{
switch (m_OutChannel)
{
case 1:
return m_ChannelMixer.outGreen;
case 2:
return m_ChannelMixer.outBlue;
default:
return m_ChannelMixer.outRed;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 74a26e3294ebad94fa5d78ee95751556
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,83 @@
using System;
using System.Reflection;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine;
using Color = UnityEditor.ShaderGraph.ColorNode.Color;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class ColorControlAttribute : Attribute, IControlAttribute
{
string m_Label;
ColorMode m_ColorMode;
public ColorControlAttribute(string label = null, ColorMode colorMode = ColorMode.Default)
{
m_Label = label;
m_ColorMode = colorMode;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new ColorControlView(m_Label, m_ColorMode, node, propertyInfo);
}
}
class ColorControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
Color m_Color;
ColorField m_ColorField;
public ColorControlView(string label, ColorMode colorMode, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
m_Node = node;
m_PropertyInfo = propertyInfo;
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/ColorControlView"));
if (propertyInfo.PropertyType != typeof(Color))
throw new ArgumentException("Property must be of type Color.", "propertyInfo");
label = label ?? ObjectNames.NicifyVariableName(propertyInfo.Name);
m_Color = (Color)m_PropertyInfo.GetValue(m_Node, null);
if (!string.IsNullOrEmpty(label))
Add(new Label(label));
m_ColorField = new ColorField { value = m_Color.color, hdr = m_Color.mode == ColorMode.HDR, showEyeDropper = false };
m_ColorField.RegisterValueChangedCallback(OnChange);
Add(m_ColorField);
VisualElement enumPanel = new VisualElement { name = "enumPanel" };
enumPanel.Add(new Label("Mode"));
var enumField = new EnumField(m_Color.mode);
enumField.RegisterValueChangedCallback(OnModeChanged);
enumPanel.Add(enumField);
Add(enumPanel);
}
void OnChange(ChangeEvent<UnityEngine.Color> evt)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Color Change");
m_Color.color = evt.newValue;
m_PropertyInfo.SetValue(m_Node, m_Color, null);
this.MarkDirtyRepaint();
}
void OnModeChanged(ChangeEvent<Enum> evt)
{
if (!evt.newValue.Equals(m_Color.mode))
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Change " + m_Node.name);
m_Color.mode = (ColorMode)evt.newValue;
m_ColorField.hdr = m_Color.mode == ColorMode.HDR;
m_PropertyInfo.SetValue(m_Node, m_Color, null);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 34e77444559b414f9fe81f9775229abf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,54 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class CubemapControlAttribute : Attribute, IControlAttribute
{
string m_Label;
public CubemapControlAttribute(string label = null)
{
m_Label = label;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new CubemapControlView(m_Label, node, propertyInfo);
}
}
class CubemapControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
public CubemapControlView(string label, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
m_Node = node;
m_PropertyInfo = propertyInfo;
if (propertyInfo.PropertyType != typeof(Cubemap))
throw new ArgumentException("Property must be of type Texture.", "propertyInfo");
label = label ?? ObjectNames.NicifyVariableName(propertyInfo.Name);
if (!string.IsNullOrEmpty(label))
Add(new Label(label));
var cubemapField = new ObjectField { value = (Cubemap)m_PropertyInfo.GetValue(m_Node, null), objectType = typeof(Cubemap) };
cubemapField.RegisterValueChangedCallback(OnChange);
Add(cubemapField);
}
void OnChange(ChangeEvent<UnityEngine.Object> evt)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Cubemap Change");
m_PropertyInfo.SetValue(m_Node, evt.newValue, null);
this.MarkDirtyRepaint();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: abdad6e5c36e0994290fcacce2fab066
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,33 @@
using System;
using System.Linq;
using System.Reflection;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine;
using Object = UnityEngine.Object;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class DefaultControlAttribute : Attribute, IControlAttribute
{
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
if (propertyInfo.PropertyType == typeof(Color))
return new ColorControlView(null, ColorMode.Default, node, propertyInfo);
if (typeof(Enum).IsAssignableFrom(propertyInfo.PropertyType))
return new EnumControlView(null, node, propertyInfo);
if (propertyInfo.PropertyType == typeof(Texture2D))
return new TextureControlView(null, node, propertyInfo);
if (propertyInfo.PropertyType == typeof(Texture2DArray))
return new TextureArrayControlView(null, node, propertyInfo);
if (propertyInfo.PropertyType == typeof(Texture3D))
return new Texture3DControlView(null, node, propertyInfo);
if (MultiFloatControlView.validTypes.Contains(propertyInfo.PropertyType))
return new MultiFloatControlView(null, "X", "Y", "Z", "W", node, propertyInfo);
if (typeof(Object).IsAssignableFrom(propertyInfo.PropertyType))
return new ObjectControlView(null, node, propertyInfo);
return null;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 20e01cb163f347c7819ae286c33baf85
timeCreated: 1507642280

View file

@ -0,0 +1,189 @@
using System;
using System.Globalization;
using System.Reflection;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class DielectricSpecularControlAttribute : Attribute, IControlAttribute
{
public DielectricSpecularControlAttribute()
{
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new DielectricSpecularControlView(node, propertyInfo);
}
}
class DielectricSpecularControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
DielectricSpecularNode.DielectricMaterial m_DielectricMaterial;
VisualElement m_RangePanel;
Slider m_RangeSlider;
FloatField m_RangeField;
VisualElement m_IORPanel;
Slider m_IORSlider;
FloatField m_IORField;
public DielectricSpecularControlView(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
m_Node = node;
m_PropertyInfo = propertyInfo;
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/DielectricSpecularControlView"));
m_DielectricMaterial = (DielectricSpecularNode.DielectricMaterial)m_PropertyInfo.GetValue(m_Node, null);
if (propertyInfo.PropertyType != typeof(DielectricSpecularNode.DielectricMaterial))
throw new ArgumentException("Property must be of type DielectricMaterial.", "propertyInfo");
var enumPanel = new VisualElement { name = "enumPanel" };
enumPanel.Add(new Label("Material"));
var enumField = new EnumField(m_DielectricMaterial.type);
enumField.RegisterValueChangedCallback(OnEnumChanged);
enumPanel.Add(enumField);
Add(enumPanel);
m_RangePanel = new VisualElement { name = "sliderPanel" };
m_RangePanel.Add(new Label("Range"));
m_RangeSlider = new Slider(0.01f, 1) { value = m_DielectricMaterial.range };
m_RangeSlider.RegisterValueChangedCallback((evt) => OnChangeRangeSlider(evt.newValue));
m_RangePanel.Add(m_RangeSlider);
m_RangeField = AddField(m_RangePanel, m_RangeSlider, 0, m_DielectricMaterial);
m_RangePanel.SetEnabled(m_DielectricMaterial.type == DielectricMaterialType.Common);
Add(m_RangePanel);
m_IORPanel = new VisualElement { name = "sliderPanel" };
m_IORPanel.Add(new Label("IOR"));
m_IORSlider = new Slider(1, 2.5f) { value = m_DielectricMaterial.indexOfRefraction };
m_IORSlider.RegisterValueChangedCallback((evt) => OnChangeIORSlider(evt.newValue));
m_IORPanel.Add(m_IORSlider);
m_IORField = AddField(m_IORPanel, m_IORSlider, 1, m_DielectricMaterial);
m_IORPanel.SetEnabled(m_DielectricMaterial.type == DielectricMaterialType.Custom);
Add(m_IORPanel);
}
void OnEnumChanged(ChangeEvent<Enum> evt)
{
if (!evt.newValue.Equals(m_DielectricMaterial.type))
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Change " + m_Node.name);
m_DielectricMaterial.type = (DielectricMaterialType)evt.newValue;
m_PropertyInfo.SetValue(m_Node, m_DielectricMaterial, null);
switch (m_DielectricMaterial.type)
{
case DielectricMaterialType.Common:
m_RangePanel.SetEnabled(true);
m_IORPanel.SetEnabled(false);
break;
case DielectricMaterialType.Custom:
m_RangePanel.SetEnabled(false);
m_IORPanel.SetEnabled(true);
break;
default:
m_RangePanel.SetEnabled(false);
m_IORPanel.SetEnabled(false);
break;
}
}
}
void OnChangeRangeSlider(float newValue)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Slider Change");
m_DielectricMaterial.range = newValue;
m_PropertyInfo.SetValue(m_Node, m_DielectricMaterial, null);
if (m_RangeField != null)
m_RangeField.value = newValue;
this.MarkDirtyRepaint();
}
void OnChangeIORSlider(float newValue)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Slider Change");
m_DielectricMaterial.indexOfRefraction = newValue;
m_PropertyInfo.SetValue(m_Node, m_DielectricMaterial, null);
if (m_IORField != null)
m_IORField.value = newValue;
this.MarkDirtyRepaint();
}
FloatField AddField(VisualElement panel, Slider slider, int index, DielectricSpecularNode.DielectricMaterial initMaterial)
{
float initValue;
if (index == 1)
initValue = initMaterial.indexOfRefraction;
else
initValue = initMaterial.range;
var field = new FloatField { userData = index, value = initValue };
field.RegisterCallback<MouseDownEvent>(Repaint);
field.RegisterCallback<MouseMoveEvent>(Repaint);
field.RegisterValueChangedCallback(evt =>
{
var fieldValue = (float)evt.newValue;
if (index == 1)
m_DielectricMaterial.indexOfRefraction = fieldValue;
else
m_DielectricMaterial.range = fieldValue;
m_PropertyInfo.SetValue(m_Node, m_DielectricMaterial, null);
this.MarkDirtyRepaint();
});
field.Q("unity-text-input").RegisterCallback<FocusOutEvent>(evt =>
{
if (index == 1)
RedrawIORControls(m_DielectricMaterial.indexOfRefraction);
else
RedrawRangeControls(m_DielectricMaterial.range);
this.MarkDirtyRepaint();
});
panel.Add(field);
return field;
}
void RedrawRangeControls(float value)
{
value = Mathf.Max(Mathf.Min(value, 1), 0.01f);
m_RangePanel.Remove(m_RangeSlider);
m_RangeSlider = new Slider(0.01f, 1) { value = value };
m_RangeSlider.RegisterValueChangedCallback((evt) => OnChangeRangeSlider(evt.newValue));
m_RangePanel.Add(m_RangeSlider);
m_RangePanel.Remove(m_RangeField);
m_RangeField.value = value;
m_RangePanel.Add(m_RangeField);
}
void RedrawIORControls(float value)
{
value = Mathf.Max(Mathf.Min(value, 5), 1);
m_IORPanel.Remove(m_IORSlider);
m_IORSlider = new Slider(1, 2.5f) { value = value };
m_IORSlider.RegisterValueChangedCallback((evt) => OnChangeIORSlider(evt.newValue));
m_IORPanel.Add(m_IORSlider);
m_IORPanel.Remove(m_IORField);
m_IORField.value = value;
m_IORPanel.Add(m_IORField);
}
void Repaint<T>(MouseEventBase<T> evt) where T : MouseEventBase<T>, new()
{
evt.StopPropagation();
this.MarkDirtyRepaint();
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 20b1492eed8154d8f9a454e3563d9c01
timeCreated: 1507817885

View file

@ -0,0 +1,53 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class EnumControlAttribute : Attribute, IControlAttribute
{
string m_Label;
public EnumControlAttribute(string label = null)
{
m_Label = label;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new EnumControlView(m_Label, node, propertyInfo);
}
}
class EnumControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
public EnumControlView(string label, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/EnumControlView"));
m_Node = node;
m_PropertyInfo = propertyInfo;
if (!propertyInfo.PropertyType.IsEnum)
throw new ArgumentException("Property must be an enum.", "propertyInfo");
Add(new Label(label ?? ObjectNames.NicifyVariableName(propertyInfo.Name)));
var enumField = new EnumField((Enum)m_PropertyInfo.GetValue(m_Node, null));
enumField.RegisterValueChangedCallback(OnValueChanged);
Add(enumField);
}
void OnValueChanged(ChangeEvent<Enum> evt)
{
var value = (Enum)m_PropertyInfo.GetValue(m_Node, null);
if (!evt.newValue.Equals(value))
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Change " + m_Node.name);
m_PropertyInfo.SetValue(m_Node, evt.newValue, null);
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3a1e13c5e67541d7ad7ae18ea5a834e4
timeCreated: 1507817885

View file

@ -0,0 +1,83 @@
using System;
using System.Linq;
using System.Reflection;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
interface IEnumConversion
{
Enum from { get; set; }
Enum to { get; set; }
}
[AttributeUsage(AttributeTargets.Property)]
class EnumConversionControlAttribute : Attribute, IControlAttribute
{
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new EnumConversionControlView(node, propertyInfo);
}
}
class EnumConversionControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
IEnumConversion value
{
get { return (IEnumConversion)m_PropertyInfo.GetValue(m_Node, null); }
set { m_PropertyInfo.SetValue(m_Node, value, null); }
}
public EnumConversionControlView(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
if (!propertyInfo.PropertyType.GetInterfaces().Any(t => t == typeof(IEnumConversion)))
throw new ArgumentException("Property type must implement IEnumConversion.");
m_Node = node;
m_PropertyInfo = propertyInfo;
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/EnumConversionControlView"));
var currentValue = value;
var ec = (IEnumConversion)propertyInfo.GetValue(m_Node, null);
propertyInfo.SetValue(m_Node, ec, null);
var fromField = new EnumField(currentValue.from);
fromField.RegisterValueChangedCallback(OnFromChanged);
Add(fromField);
var arrowLabel = new Label("➔");
Add(arrowLabel);
var toField = new EnumField(currentValue.to);
toField.RegisterValueChangedCallback(OnToChanged);
Add(toField);
}
void OnFromChanged(ChangeEvent<Enum> evt)
{
var currentValue = value;
if (!Equals(currentValue.from, evt.newValue))
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Change Colorspace From");
currentValue.from = evt.newValue;
value = currentValue;
}
}
void OnToChanged(ChangeEvent<Enum> evt)
{
var currentValue = value;
if (!Equals(currentValue.to, evt.newValue))
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Change Colorspace To");
currentValue.to = evt.newValue;
value = currentValue;
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 50c6ddf46f8445fba2689962d438c227
timeCreated: 1512640228

View file

@ -0,0 +1,92 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor.Graphing;
using UnityEditor.ShaderGraph;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class GradientControlAttribute : Attribute, IControlAttribute
{
string m_Label;
public GradientControlAttribute(string label = null)
{
m_Label = label;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new GradientControlView(m_Label, node, propertyInfo);
}
}
[Serializable]
class GradientObject : ScriptableObject
{
public Gradient gradient = new Gradient();
}
class GradientControlView : VisualElement
{
GUIContent m_Label;
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
[SerializeField]
GradientObject m_GradientObject;
[SerializeField]
SerializedObject m_SerializedObject;
public GradientControlView(string label, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
m_Node = node;
m_PropertyInfo = propertyInfo;
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/GradientControlView"));
if (propertyInfo.PropertyType != typeof(Gradient))
throw new ArgumentException("Property must be of type Gradient.", "propertyInfo");
new GUIContent(label ?? ObjectNames.NicifyVariableName(propertyInfo.Name));
m_GradientObject = ScriptableObject.CreateInstance<GradientObject>();
m_GradientObject.gradient = new Gradient();
m_SerializedObject = new SerializedObject(m_GradientObject);
var gradient = (Gradient)m_PropertyInfo.GetValue(m_Node, null);
m_GradientObject.gradient.SetKeys(gradient.colorKeys, gradient.alphaKeys);
m_GradientObject.gradient.mode = gradient.mode;
var gradientPanel = new VisualElement { name = "gradientPanel" };
if (!string.IsNullOrEmpty(label))
gradientPanel.Add(new Label(label));
var gradientField = new GradientField() { value = m_GradientObject.gradient, colorSpace = ColorSpace.Linear };
gradientField.RegisterValueChangedCallback(OnValueChanged);
gradientPanel.Add(gradientField);
Add(gradientPanel);
}
void OnValueChanged(ChangeEvent<Gradient> evt)
{
m_SerializedObject.Update();
var value = (Gradient)m_PropertyInfo.GetValue(m_Node, null);
if (!evt.newValue.Equals(value))
{
m_GradientObject.gradient.SetKeys(evt.newValue.colorKeys, evt.newValue.alphaKeys);
m_GradientObject.gradient.mode = evt.newValue.mode;
m_SerializedObject.ApplyModifiedProperties();
m_Node.owner.owner.RegisterCompleteObjectUndo("Change " + m_Node.name);
m_PropertyInfo.SetValue(m_Node, m_GradientObject.gradient, null);
this.MarkDirtyRepaint();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cd2268cf57344487791694f9f136712e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
using System.Reflection;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
interface IControlAttribute
{
VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo);
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5f008211c3394775bdefde0b20e5c8ff
timeCreated: 1507642348

View file

@ -0,0 +1,56 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class IdentifierControlAttribute : Attribute, IControlAttribute
{
string m_Label;
public IdentifierControlAttribute(string label = null)
{
m_Label = label;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new IdentifierControlView(m_Label, node, propertyInfo);
}
}
class IdentifierControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
public IdentifierControlView(string label, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
var style = Resources.Load<StyleSheet>("Styles/Controls/IdentifierControlView");
if (style) styleSheets.Add(style);
m_Node = node;
m_PropertyInfo = propertyInfo;
if (propertyInfo.PropertyType != typeof(string))
throw new ArgumentException("Property must be of type string.", "propertyInfo");
label = label ?? ObjectNames.NicifyVariableName(propertyInfo.Name);
if (!string.IsNullOrEmpty(label))
Add(new Label(label));
var strField = new IdentifierField() { value = (string)m_PropertyInfo.GetValue(m_Node, null) };
strField.RegisterValueChangedCallback(OnChange);
Add(strField);
}
void OnChange(ChangeEvent<string> evt)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Identifier Change");
m_PropertyInfo.SetValue(m_Node, evt.newValue, null);
this.MarkDirtyRepaint();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a0e142b8abd53a243b029b58247caacc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,55 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class IntegerControlAttribute : Attribute, IControlAttribute
{
string m_Label;
public IntegerControlAttribute(string label = null)
{
m_Label = label;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new IntegerControlView(m_Label, node, propertyInfo);
}
}
class IntegerControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
public IntegerControlView(string label, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/IntegerControlView"));
m_Node = node;
m_PropertyInfo = propertyInfo;
if (propertyInfo.PropertyType != typeof(int))
throw new ArgumentException("Property must be of type integer.", "propertyInfo");
label = label ?? ObjectNames.NicifyVariableName(propertyInfo.Name);
if (!string.IsNullOrEmpty(label))
Add(new Label(label));
var intField = new IntegerField { value = (int)m_PropertyInfo.GetValue(m_Node, null) };
intField.RegisterValueChangedCallback(OnChange);
Add(intField);
}
void OnChange(ChangeEvent<int> evt)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Integer Change");
m_PropertyInfo.SetValue(m_Node, evt.newValue, null);
this.MarkDirtyRepaint();
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 57d18a57866821542806ab5253cf4310
timeCreated: 1507817885

View file

@ -0,0 +1,58 @@
using System;
using System.Reflection;
using UnityEngine;
using Object = UnityEngine.Object;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class ObjectControlAttribute : Attribute, IControlAttribute
{
string m_Label;
public ObjectControlAttribute(string label = null)
{
m_Label = label;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new ObjectControlView(m_Label, node, propertyInfo);
}
}
class ObjectControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
public ObjectControlView(string label, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
if (!typeof(Object).IsAssignableFrom(propertyInfo.PropertyType))
throw new ArgumentException("Property must be assignable to UnityEngine.Object.");
m_Node = node;
m_PropertyInfo = propertyInfo;
label = label ?? propertyInfo.Name;
if (!string.IsNullOrEmpty(label))
Add(new Label { text = label });
var value = (Object)m_PropertyInfo.GetValue(m_Node, null);
var objectField = new ObjectField { objectType = propertyInfo.PropertyType, value = value };
objectField.RegisterValueChangedCallback(OnValueChanged);
Add(objectField);
}
void OnValueChanged(ChangeEvent<Object> evt)
{
var value = (Object)m_PropertyInfo.GetValue(m_Node, null);
if (evt.newValue != value)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Change + " + m_Node.name);
m_PropertyInfo.SetValue(m_Node, evt.newValue, null);
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3f483132ae7b4689b20fd6b8c59633b5
timeCreated: 1507900115

View file

@ -0,0 +1,75 @@
using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class PopupControlAttribute : Attribute, IControlAttribute
{
string m_Label;
//string[] m_Entries;
public PopupControlAttribute(string label = null)
{
m_Label = label;
//m_Entries = entries;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new PopupControlView(m_Label, node, propertyInfo);
}
}
[Serializable]
struct PopupList
{
public int selectedEntry;
public string[] popupEntries;
public PopupList(string[] entries, int defaultEntry)
{
popupEntries = entries;
selectedEntry = defaultEntry;
}
}
class PopupControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
PopupField<string> m_PopupField;
public PopupControlView(string label, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/PopupControlView"));
m_Node = node;
m_PropertyInfo = propertyInfo;
Type type = propertyInfo.PropertyType;
if (type != typeof(PopupList))
{
throw new ArgumentException("Property must be a PopupList.", "propertyInfo");
}
Add(new Label(label ?? ObjectNames.NicifyVariableName(propertyInfo.Name)));
var value = (PopupList)propertyInfo.GetValue(m_Node, null);
m_PopupField = new PopupField<string>(new List<string>(value.popupEntries), value.selectedEntry);
m_PopupField.RegisterValueChangedCallback(OnValueChanged);
Add(m_PopupField);
}
void OnValueChanged(ChangeEvent<string> evt)
{
var value = (PopupList)m_PropertyInfo.GetValue(m_Node, null);
value.selectedEntry = m_PopupField.index;
m_PropertyInfo.SetValue(m_Node, value, null);
m_Node.owner.owner.RegisterCompleteObjectUndo("Change " + m_Node.name);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 348b3c9679acca945b0df8cb701908f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,169 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor.Graphing;
using System.Globalization;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class SliderControlAttribute : Attribute, IControlAttribute
{
string m_Label;
bool m_DisplayMinMax;
public SliderControlAttribute(string label = null, bool displayMinMax = false)
{
m_Label = label;
m_DisplayMinMax = displayMinMax;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new SliderControlView(m_Label, m_DisplayMinMax, node, propertyInfo);
}
}
class SliderControlView : VisualElement, AbstractMaterialNodeModificationListener
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
bool m_DisplayMinMax;
Vector3 m_Value;
VisualElement m_SliderPanel;
Slider m_Slider;
FloatField m_SliderInput;
FloatField m_MinField;
FloatField m_MaxField;
public SliderControlView(string label, bool displayMinMax, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
m_Node = node;
m_PropertyInfo = propertyInfo;
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/SliderControlView"));
m_DisplayMinMax = displayMinMax;
if (propertyInfo.PropertyType != typeof(Vector3))
throw new ArgumentException("Property must be of type Vector3.", "propertyInfo");
new GUIContent(label ?? ObjectNames.NicifyVariableName(propertyInfo.Name));
m_Value = (Vector3)m_PropertyInfo.GetValue(m_Node, null);
m_Slider = new Slider(m_Value.y, m_Value.z) { value = m_Value.x };
m_Slider.RegisterValueChangedCallback((evt) => OnChangeSlider(evt.newValue));
m_SliderInput = new FloatField { value = m_Value.x };
m_SliderInput.RegisterValueChangedCallback(evt =>
{
var value = (float)evt.newValue;
m_Value.x = value;
m_PropertyInfo.SetValue(m_Node, m_Value, null);
this.MarkDirtyRepaint();
});
m_SliderInput.Q("unity-text-input").RegisterCallback<FocusOutEvent>(evt =>
{
float minValue = Mathf.Min(m_Value.x, m_Value.y);
float maxValue = Mathf.Max(m_Value.x, m_Value.z);
m_Value = new Vector3(m_Value.x, minValue, maxValue);
m_MinField.value = minValue;
m_MaxField.value = maxValue;
UpdateSlider();
m_PropertyInfo.SetValue(m_Node, m_Value, null);
this.MarkDirtyRepaint();
});
m_SliderPanel = new VisualElement { name = "SliderPanel" };
if (!string.IsNullOrEmpty(label))
m_SliderPanel.Add(new Label(label));
m_SliderPanel.Add(m_Slider);
m_SliderPanel.Add(m_SliderInput);
Add(m_SliderPanel);
if (m_DisplayMinMax)
{
var fieldsPanel = new VisualElement { name = "FieldsPanel" };
m_MinField = AddMinMaxField(fieldsPanel, "Min", 1);
m_MaxField = AddMinMaxField(fieldsPanel, "Max", 2);
Add(fieldsPanel);
}
}
public void OnNodeModified(ModificationScope scope)
{
if (scope == ModificationScope.Graph)
{
this.MarkDirtyRepaint();
}
}
void OnChangeSlider(float newValue)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Slider Change");
var value = (Vector3)m_PropertyInfo.GetValue(m_Node, null);
value.x = newValue;
m_PropertyInfo.SetValue(m_Node, value, null);
if (m_SliderInput != null)
m_SliderInput.value = newValue;
m_PropertyInfo.SetValue(m_Node, m_Value, null);
this.MarkDirtyRepaint();
}
void UpdateSlider()
{
m_SliderPanel.Remove(m_Slider);
m_Slider = new Slider(m_Value.y, m_Value.z) { value = m_Value.x };
m_Slider.RegisterValueChangedCallback((evt) => OnChangeSlider(evt.newValue));
m_SliderPanel.Add(m_Slider);
m_SliderPanel.Add(m_SliderInput);
}
FloatField AddMinMaxField(VisualElement panel, string label, int index)
{
var floatField = new FloatField { value = m_Value[index] };
if (label != null)
{
var labelField = new Label(label);
panel.Add(labelField);
}
floatField.RegisterValueChangedCallback(evt =>
{
m_Value[index] = (float)evt.newValue;
m_PropertyInfo.SetValue(m_Node, m_Value, null);
this.MarkDirtyRepaint();
});
floatField.Q("unity-text-input").RegisterCallback<FocusOutEvent>(evt =>
{
if (index == 1)
{
m_Value[index] = Mathf.Min(m_Value[index], m_Value.z);
m_MinField.value = m_Value[index];
}
else
{
m_Value[index] = Mathf.Max(m_Value[index], m_Value.y);
m_MaxField.value = m_Value[index];
}
float newValue = Mathf.Max(Mathf.Min(m_Value.x, m_Value.z), m_Value.y);
m_Value.x = newValue;
m_SliderInput.value = newValue;
UpdateSlider();
m_PropertyInfo.SetValue(m_Node, m_Value, null);
this.MarkDirtyRepaint();
});
panel.Add(floatField);
return floatField;
}
void Repaint<T>(MouseEventBase<T> evt) where T : MouseEventBase<T>, new()
{
evt.StopPropagation();
this.MarkDirtyRepaint();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c74a50771637c2d49980f713bbb7f4ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,103 @@
using System;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEngine.Assertions;
using UnityEditor.Graphing;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class TextControlAttribute : Attribute, IControlAttribute
{
string m_Label;
public TextControlAttribute(string label = null)
{
m_Label = label;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
if (!TextControlView.validTypes.Contains(propertyInfo.PropertyType))
return null;
return new TextControlView(m_Label, node, propertyInfo);
}
}
class TextControlView : VisualElement
{
public static Type[] validTypes = { typeof(string) };
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
string m_Value;
int m_UndoGroup = -1;
public TextControlView(string label, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/TextControlView"));
m_Node = node;
m_PropertyInfo = propertyInfo;
label = label ?? ObjectNames.NicifyVariableName(propertyInfo.Name);
var container = new VisualElement { name = "container" };
var thisLabel = new Label(label);
container.Add(thisLabel);
m_Value = GetValue();
string value = null;
var field = new TextField { value = m_Value };
field.RegisterCallback<MouseDownEvent>(Repaint);
field.RegisterCallback<MouseMoveEvent>(Repaint);
field.RegisterValueChangedCallback(evt =>
{
value = GetValue();
value = evt.newValue;
if (m_Node.GetType() != typeof(SwizzleNode))
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Change" + m_Node.name);
m_PropertyInfo.SetValue(m_Node, value, null);
m_UndoGroup = -1;
}
});
// Pressing escape while we are editing causes it to revert to the original value when we gained focus
field.Q("unity-text-input").RegisterCallback<KeyDownEvent>(evt =>
{
if (evt.keyCode == KeyCode.Escape && m_UndoGroup > -1)
{
Undo.RevertAllDownToGroup(m_UndoGroup);
m_UndoGroup = -1;
evt.StopPropagation();
}
this.MarkDirtyRepaint();
});
field.Q("unity-text-input").RegisterCallback<FocusOutEvent>(evt =>
{
if (m_Node.GetType() == typeof(SwizzleNode))
{
//Only set node value when mouse clicked away
m_Node.owner.owner.RegisterCompleteObjectUndo("Change" + m_Node.name);
m_PropertyInfo.SetValue(m_Node, value, null);
m_UndoGroup = -1;
//Validate graph to update downstream input slot
m_Node.owner.ValidateGraph();
m_Node.Dirty(ModificationScope.Topological);
}
this.MarkDirtyRepaint();
});
container.Add(field);
Add(container);
}
string GetValue()
{
var value = m_PropertyInfo.GetValue(m_Node, null);
Assert.IsNotNull(value);
return (string)value;
}
void Repaint<T>(MouseEventBase<T> evt) where T : MouseEventBase<T>, new()
{
evt.StopPropagation();
this.MarkDirtyRepaint();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 29c513b592822a84d99669e7845b9d45
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,53 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class Texture3DControlAttribute : Attribute, IControlAttribute
{
string m_Label;
public Texture3DControlAttribute(string label = null)
{
m_Label = label;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new Texture3DControlView(m_Label, node, propertyInfo);
}
}
class Texture3DControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
public Texture3DControlView(string label, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
m_Node = node;
m_PropertyInfo = propertyInfo;
if (propertyInfo.PropertyType != typeof(Texture3D))
throw new ArgumentException("Property must be of type Texture 3D.", "propertyInfo");
label = label ?? ObjectNames.NicifyVariableName(propertyInfo.Name);
if (!string.IsNullOrEmpty(label))
Add(new Label(label));
var textureField = new ObjectField { value = (Texture3D)m_PropertyInfo.GetValue(m_Node, null), objectType = typeof(Texture3D) };
textureField.RegisterValueChangedCallback(OnChange);
Add(textureField);
}
void OnChange(ChangeEvent<UnityEngine.Object> evt)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Texture Change");
m_PropertyInfo.SetValue(m_Node, evt.newValue, null);
this.MarkDirtyRepaint();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 841bd408b769d48caa3011ed9219df24
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,53 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class TextureArrayControlAttribute : Attribute, IControlAttribute
{
string m_Label;
public TextureArrayControlAttribute(string label = null)
{
m_Label = label;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new TextureArrayControlView(m_Label, node, propertyInfo);
}
}
class TextureArrayControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
public TextureArrayControlView(string label, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
m_Node = node;
m_PropertyInfo = propertyInfo;
if (propertyInfo.PropertyType != typeof(Texture2DArray))
throw new ArgumentException("Property must be of type Texture 2D Array.", "propertyInfo");
label = label ?? ObjectNames.NicifyVariableName(propertyInfo.Name);
if (!string.IsNullOrEmpty(label))
Add(new Label(label));
var textureField = new ObjectField { value = (Texture2DArray)m_PropertyInfo.GetValue(m_Node, null), objectType = typeof(Texture2DArray) };
textureField.RegisterValueChangedCallback(OnChange);
Add(textureField);
}
void OnChange(ChangeEvent<UnityEngine.Object> evt)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Texture Change");
m_PropertyInfo.SetValue(m_Node, evt.newValue, null);
this.MarkDirtyRepaint();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0e3e309a7d3ff4d2e81a55970ef9b026
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,53 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class TextureControlAttribute : Attribute, IControlAttribute
{
string m_Label;
public TextureControlAttribute(string label = null)
{
m_Label = label;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new TextureControlView(m_Label, node, propertyInfo);
}
}
class TextureControlView : VisualElement
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
public TextureControlView(string label, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
m_Node = node;
m_PropertyInfo = propertyInfo;
if (propertyInfo.PropertyType != typeof(Texture))
throw new ArgumentException("Property must be of type Texture.", "propertyInfo");
label = label ?? ObjectNames.NicifyVariableName(propertyInfo.Name);
if (!string.IsNullOrEmpty(label))
Add(new Label(label));
var textureField = new ObjectField { value = (Texture)m_PropertyInfo.GetValue(m_Node, null), objectType = typeof(Texture) };
textureField.RegisterValueChangedCallback(OnChange);
Add(textureField);
}
void OnChange(ChangeEvent<UnityEngine.Object> evt)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Texture Change");
m_PropertyInfo.SetValue(m_Node, evt.newValue, null);
this.MarkDirtyRepaint();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 12334ec97337f32408251974c4077033
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,102 @@
using System;
using System.Reflection;
using UnityEditor.Graphing;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[Serializable]
struct ToggleData
{
public bool isOn;
public bool isEnabled;
public ToggleData(bool on, bool enabled)
{
isOn = on;
isEnabled = enabled;
}
public ToggleData(bool on)
{
isOn = on;
isEnabled = true;
}
}
[AttributeUsage(AttributeTargets.Property)]
class ToggleControlAttribute : Attribute, IControlAttribute
{
string m_Label;
public ToggleControlAttribute(string label = null)
{
m_Label = label;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
return new ToggleControlView(m_Label, node, propertyInfo);
}
}
class ToggleControlView : VisualElement, AbstractMaterialNodeModificationListener
{
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
Label m_Label;
Toggle m_Toggle;
public ToggleControlView(string label, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
m_Node = node;
m_PropertyInfo = propertyInfo;
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/ToggleControlView"));
if (propertyInfo.PropertyType != typeof(ToggleData))
throw new ArgumentException("Property must be a Toggle.", "propertyInfo");
label = label ?? ObjectNames.NicifyVariableName(propertyInfo.Name);
var value = (ToggleData)m_PropertyInfo.GetValue(m_Node, null);
var panel = new VisualElement { name = "togglePanel" };
if (!string.IsNullOrEmpty(label))
{
m_Label = new Label(label);
m_Label.SetEnabled(value.isEnabled);
panel.Add(m_Label);
}
m_Toggle = new Toggle();
m_Toggle.OnToggleChanged(OnChangeToggle);
m_Toggle.SetEnabled(value.isEnabled);
m_Toggle.value = value.isOn;
panel.Add(m_Toggle);
Add(panel);
}
public void OnNodeModified(ModificationScope scope)
{
var value = (ToggleData)m_PropertyInfo.GetValue(m_Node, null);
m_Toggle.SetEnabled(value.isEnabled);
if (m_Label != null)
m_Label.SetEnabled(value.isEnabled);
if (scope == ModificationScope.Graph)
{
this.MarkDirtyRepaint();
}
}
void OnChangeToggle(ChangeEvent<bool> evt)
{
m_Node.owner.owner.RegisterCompleteObjectUndo("Toggle Change");
var value = (ToggleData)m_PropertyInfo.GetValue(m_Node, null);
value.isOn = evt.newValue;
m_PropertyInfo.SetValue(m_Node, value, null);
this.MarkDirtyRepaint();
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5e83ab8eb10c20c4fbed2700d4f4f11c
timeCreated: 1507817885

View file

@ -0,0 +1,152 @@
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Controls
{
[AttributeUsage(AttributeTargets.Property)]
class MultiFloatControlAttribute : Attribute, IControlAttribute
{
string m_Label;
string m_SubLabel1;
string m_SubLabel2;
string m_SubLabel3;
string m_SubLabel4;
public MultiFloatControlAttribute(string label = null, string subLabel1 = "X", string subLabel2 = "Y", string subLabel3 = "Z", string subLabel4 = "W")
{
m_SubLabel1 = subLabel1;
m_SubLabel2 = subLabel2;
m_SubLabel3 = subLabel3;
m_SubLabel4 = subLabel4;
m_Label = label;
}
public VisualElement InstantiateControl(AbstractMaterialNode node, PropertyInfo propertyInfo)
{
if (!MultiFloatControlView.validTypes.Contains(propertyInfo.PropertyType))
return null;
return new MultiFloatControlView(m_Label, m_SubLabel1, m_SubLabel2, m_SubLabel3, m_SubLabel4, node, propertyInfo);
}
}
class MultiFloatControlView : VisualElement
{
public static Type[] validTypes = { typeof(float), typeof(Vector2), typeof(Vector3), typeof(Vector4) };
AbstractMaterialNode m_Node;
PropertyInfo m_PropertyInfo;
Vector4 m_Value;
int m_UndoGroup = -1;
public MultiFloatControlView(string label, string subLabel1, string subLabel2, string subLabel3, string subLabel4, AbstractMaterialNode node, PropertyInfo propertyInfo)
{
var components = Array.IndexOf(validTypes, propertyInfo.PropertyType) + 1;
if (components == -1)
throw new ArgumentException("Property must be of type float, Vector2, Vector3 or Vector4.", "propertyInfo");
styleSheets.Add(Resources.Load<StyleSheet>("Styles/Controls/MultiFloatControlView"));
m_Node = node;
m_PropertyInfo = propertyInfo;
label = label ?? ObjectNames.NicifyVariableName(propertyInfo.Name);
if (!string.IsNullOrEmpty(label))
Add(new Label(label));
m_Value = GetValue();
AddField(0, subLabel1);
if (components > 1)
AddField(1, subLabel2);
if (components > 2)
AddField(2, subLabel3);
if (components > 3)
AddField(3, subLabel4);
}
void AddField(int index, string subLabel)
{
var dummy = new VisualElement { name = "dummy" };
var label = new Label(subLabel);
dummy.Add(label);
Add(dummy);
var field = new FloatField { userData = index, value = m_Value[index] };
var dragger = new FieldMouseDragger<double>(field);
dragger.SetDragZone(label);
field.RegisterCallback<MouseDownEvent>(Repaint);
field.RegisterCallback<MouseMoveEvent>(Repaint);
field.RegisterValueChangedCallback(evt =>
{
var value = GetValue();
value[index] = (float)evt.newValue;
SetValue(value);
m_UndoGroup = -1;
this.MarkDirtyRepaint();
});
field.Q("unity-text-input").RegisterCallback<InputEvent>(evt =>
{
if (m_UndoGroup == -1)
{
m_UndoGroup = Undo.GetCurrentGroup();
m_Node.owner.owner.RegisterCompleteObjectUndo("Change " + m_Node.name);
}
float newValue;
if (!float.TryParse(evt.newData, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out newValue))
newValue = 0f;
var value = GetValue();
value[index] = newValue;
SetValue(value);
this.MarkDirtyRepaint();
});
field.Q("unity-text-input").RegisterCallback<KeyDownEvent>(evt =>
{
if (evt.keyCode == KeyCode.Escape && m_UndoGroup > -1)
{
Undo.RevertAllDownToGroup(m_UndoGroup);
m_UndoGroup = -1;
m_Value = GetValue();
evt.StopPropagation();
}
this.MarkDirtyRepaint();
});
Add(field);
}
object ValueToPropertyType(Vector4 value)
{
if (m_PropertyInfo.PropertyType == typeof(float))
return value.x;
if (m_PropertyInfo.PropertyType == typeof(Vector2))
return (Vector2)value;
if (m_PropertyInfo.PropertyType == typeof(Vector3))
return (Vector3)value;
return value;
}
Vector4 GetValue()
{
var value = m_PropertyInfo.GetValue(m_Node, null);
if (m_PropertyInfo.PropertyType == typeof(float))
return new Vector4((float)value, 0f, 0f, 0f);
if (m_PropertyInfo.PropertyType == typeof(Vector2))
return (Vector2)value;
if (m_PropertyInfo.PropertyType == typeof(Vector3))
return (Vector3)value;
return (Vector4)value;
}
void SetValue(Vector4 value)
{
m_PropertyInfo.SetValue(m_Node, ValueToPropertyType(value), null);
}
void Repaint<T>(MouseEventBase<T> evt) where T : MouseEventBase<T>, new()
{
evt.StopPropagation();
this.MarkDirtyRepaint();
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8a515dc1bacc453b870777d8da9a7211
timeCreated: 1507642288

View file

@ -0,0 +1,44 @@
using UnityEngine;
using UnityEditor.Experimental.GraphView;
using Edge = UnityEditor.Experimental.GraphView.Edge;
using UnityEditor.Searcher;
namespace UnityEditor.ShaderGraph.Drawing
{
class EdgeConnectorListener : IEdgeConnectorListener
{
readonly GraphData m_Graph;
readonly SearchWindowProvider m_SearchWindowProvider;
readonly EditorWindow m_editorWindow;
public EdgeConnectorListener(GraphData graph, SearchWindowProvider searchWindowProvider, EditorWindow editorWindow)
{
m_Graph = graph;
m_SearchWindowProvider = searchWindowProvider;
m_editorWindow = editorWindow;
}
public void OnDropOutsidePort(Edge edge, Vector2 position)
{
var draggedPort = (edge.output != null ? edge.output.edgeConnector.edgeDragHelper.draggedPort : null) ?? (edge.input != null ? edge.input.edgeConnector.edgeDragHelper.draggedPort : null);
m_SearchWindowProvider.target = null;
m_SearchWindowProvider.connectedPort = (ShaderPort)draggedPort;
m_SearchWindowProvider.regenerateEntries = true;//need to be sure the entires are relevant to the edge we are dragging
SearcherWindow.Show(m_editorWindow, (m_SearchWindowProvider as SearcherProvider).LoadSearchWindow(),
item => (m_SearchWindowProvider as SearcherProvider).OnSearcherSelectEntry(item, position),
position, null);
m_SearchWindowProvider.regenerateEntries = true;//entries no longer necessarily relevant, need to regenerate
}
public void OnDrop(GraphView graphView, Edge edge)
{
var leftSlot = edge.output.GetSlot();
var rightSlot = edge.input.GetSlot();
if (leftSlot != null && rightSlot != null)
{
m_Graph.owner.RegisterCompleteObjectUndo("Connect Edge");
m_Graph.Connect(leftSlot.slotReference, rightSlot.slotReference);
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 36c61698900b42c9a6b5e28c3651249a
timeCreated: 1512740035

View file

@ -0,0 +1,9 @@
using UnityEditor.Graphing;
namespace UnityEditor.ShaderGraph.Drawing
{
interface AbstractMaterialNodeModificationListener
{
void OnNodeModified(ModificationScope scope);
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 22f268830a014848b3a8bfdd5142a2d0
timeCreated: 1510751718

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1d1f4aaa81084a13aaeb78dfaf7d35d7
timeCreated: 1503663057

View file

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

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6759820ca0a974f36b1a63ec6f6e1a57
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEditor.Graphing;
using UnityEditor.Graphing.Util;
using UnityEditor.ShaderGraph.Internal;
using Object = UnityEngine.Object;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
using UnityEngine.UIElements.StyleSheets;
namespace UnityEditor.ShaderGraph.Drawing.Inspector
{
class MasterPreviewView : VisualElement
{
PreviewManager m_PreviewManager;
GraphData m_Graph;
PreviewRenderData m_PreviewRenderHandle;
Image m_PreviewTextureView;
public Image previewTextureView
{
get { return m_PreviewTextureView; }
}
Vector2 m_PreviewScrollPosition;
ObjectField m_PreviewMeshPicker;
Mesh m_PreviousMesh;
bool m_RecalculateLayout;
ResizeBorderFrame m_PreviewResizeBorderFrame;
public ResizeBorderFrame previewResizeBorderFrame
{
get { return m_PreviewResizeBorderFrame; }
}
VisualElement m_Preview;
Label m_Title;
public VisualElement preview
{
get { return m_Preview; }
}
List<string> m_DoNotShowPrimitives = new List<string>(new string[] { PrimitiveType.Plane.ToString() });
static Type s_ContextualMenuManipulator = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypesOrNothing()).FirstOrDefault(t => t.FullName == "UnityEngine.UIElements.ContextualMenuManipulator");
static Type s_ObjectSelector = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypesOrNothing()).FirstOrDefault(t => t.FullName == "UnityEditor.ObjectSelector");
public string assetName
{
get { return m_Title.text; }
set { m_Title.text = value; }
}
public MasterPreviewView(PreviewManager previewManager, GraphData graph)
{
style.overflow = Overflow.Hidden;
m_PreviewManager = previewManager;
m_Graph = graph;
styleSheets.Add(Resources.Load<StyleSheet>("Styles/MasterPreviewView"));
m_PreviewRenderHandle = previewManager.masterRenderData;
if (m_PreviewRenderHandle != null)
{
m_PreviewRenderHandle.onPreviewChanged += OnPreviewChanged;
}
var topContainer = new VisualElement() { name = "top" };
{
m_Title = new Label() { name = "title" };
m_Title.text = "Main Preview";
topContainer.Add(m_Title);
}
Add(topContainer);
m_Preview = new VisualElement { name = "middle" };
{
m_PreviewTextureView = CreatePreview(Texture2D.blackTexture);
m_PreviewScrollPosition = new Vector2(0f, 0f);
preview.Add(m_PreviewTextureView);
preview.AddManipulator(new Scrollable(OnScroll));
}
Add(preview);
m_PreviewResizeBorderFrame = new ResizeBorderFrame(this, this) { name = "resizeBorderFrame" };
m_PreviewResizeBorderFrame.maintainAspectRatio = true;
Add(m_PreviewResizeBorderFrame);
m_RecalculateLayout = false;
this.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
}
Image CreatePreview(Texture texture)
{
if (m_PreviewRenderHandle?.texture != null)
{
texture = m_PreviewRenderHandle.texture;
}
var image = new Image { name = "preview", image = texture };
image.AddManipulator(new Draggable(OnMouseDragPreviewMesh, true));
image.AddManipulator((IManipulator)Activator.CreateInstance(s_ContextualMenuManipulator, (Action<ContextualMenuPopulateEvent>)BuildContextualMenu));
return image;
}
void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
foreach (var primitiveTypeName in Enum.GetNames(typeof(PrimitiveType)))
{
if (m_DoNotShowPrimitives.Contains(primitiveTypeName))
continue;
evt.menu.AppendAction(primitiveTypeName, e => ChangePrimitiveMesh(primitiveTypeName), DropdownMenuAction.AlwaysEnabled);
}
evt.menu.AppendAction("Sprite", e => ChangeMeshSprite(), DropdownMenuAction.AlwaysEnabled);
evt.menu.AppendAction("Custom Mesh", e => ChangeMeshCustom(), DropdownMenuAction.AlwaysEnabled);
}
void OnPreviewChanged()
{
m_PreviewTextureView.image = m_PreviewRenderHandle?.texture ?? Texture2D.blackTexture;
if (m_PreviewRenderHandle != null && m_PreviewRenderHandle.shaderData.isOutOfDate)
m_PreviewTextureView.tintColor = new Color(1.0f, 1.0f, 1.0f, 0.3f);
else
m_PreviewTextureView.tintColor = Color.white;
m_PreviewTextureView.MarkDirtyRepaint();
}
void ChangePrimitiveMesh(string primitiveName)
{
Mesh changedPrimitiveMesh = Resources.GetBuiltinResource(typeof(Mesh), string.Format("{0}.fbx", primitiveName)) as Mesh;
ChangeMesh(changedPrimitiveMesh);
}
void ChangeMesh(Mesh mesh)
{
Mesh changedMesh = mesh;
m_PreviewManager.UpdateMasterPreview(ModificationScope.Node);
if (m_Graph.previewData.serializedMesh.mesh != changedMesh)
{
m_Graph.previewData.rotation = Quaternion.identity;
m_PreviewScrollPosition = Vector2.zero;
}
m_Graph.previewData.preventRotation = false;
m_Graph.previewData.serializedMesh.mesh = changedMesh;
}
private static EditorWindow Get()
{
PropertyInfo P = s_ObjectSelector.GetProperty("get", BindingFlags.Public | BindingFlags.Static);
return P.GetValue(null, null) as EditorWindow;
}
void OnMeshChanged(Object obj)
{
var mesh = obj as Mesh;
if (mesh == null)
mesh = m_PreviousMesh;
ChangeMesh(mesh);
}
void ChangeMeshSprite()
{
ChangePrimitiveMesh(PrimitiveType.Quad.ToString());
m_Graph.previewData.rotation = Quaternion.identity;
m_Graph.previewData.preventRotation = true;
}
void ChangeMeshCustom()
{
MethodInfo ShowMethod = s_ObjectSelector.GetMethod("Show", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly, Type.DefaultBinder, new[] { typeof(Object), typeof(Type), typeof(Object), typeof(bool), typeof(List<int>), typeof(Action<Object>), typeof(Action<Object>) }, new ParameterModifier[7]);
m_PreviousMesh = m_Graph.previewData.serializedMesh.mesh;
ShowMethod.Invoke(Get(), new object[] { null, typeof(Mesh), null, false, null, (Action<Object>)OnMeshChanged, (Action<Object>)OnMeshChanged });
}
void OnGeometryChanged(GeometryChangedEvent evt)
{
if (m_RecalculateLayout)
{
WindowDockingLayout dockingLayout = new WindowDockingLayout();
dockingLayout.CalculateDockingCornerAndOffset(layout, parent.layout);
dockingLayout.ClampToParentWindow();
dockingLayout.ApplyPosition(this);
m_RecalculateLayout = false;
}
var currentWidth = m_PreviewRenderHandle?.texture != null ? m_PreviewRenderHandle.texture.width : -1;
var currentHeight = m_PreviewRenderHandle?.texture != null ? m_PreviewRenderHandle.texture.height : -1;
var targetWidth = Mathf.Max(1f, m_PreviewTextureView.contentRect.width);
var targetHeight = Mathf.Max(1f, m_PreviewTextureView.contentRect.height);
if (Mathf.Approximately(currentWidth, targetHeight) && Mathf.Approximately(currentHeight, targetWidth))
return;
m_PreviewTextureView.style.width = evt.newRect.width;
m_PreviewTextureView.style.height = evt.newRect.height - 40.0f;
m_PreviewManager.ResizeMasterPreview(new Vector2(evt.newRect.width, evt.newRect.width));
}
void OnScroll(float scrollValue)
{
float rescaleAmount = -scrollValue * .03f;
m_Graph.previewData.scale = Mathf.Clamp(m_Graph.previewData.scale + rescaleAmount, 0.2f, 5f);
m_PreviewManager.UpdateMasterPreview(ModificationScope.Node);
}
void OnMouseDragPreviewMesh(Vector2 deltaMouse)
{
if (m_Graph.previewData.preventRotation) return;
Vector2 previewSize = m_PreviewTextureView.contentRect.size;
m_PreviewScrollPosition -= deltaMouse * (Event.current.shift ? 3f : 1f) / Mathf.Min(previewSize.x, previewSize.y) * 140f;
m_PreviewScrollPosition.y = Mathf.Clamp(m_PreviewScrollPosition.y, -90f, 90f);
Quaternion previewRotation = Quaternion.Euler(m_PreviewScrollPosition.y, 0, 0) * Quaternion.Euler(0, m_PreviewScrollPosition.x, 0);
m_Graph.previewData.rotation = previewRotation;
m_PreviewManager.UpdateMasterPreview(ModificationScope.Node);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 546947d46a85b284a971a3f056495735
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,189 @@
using System;
using UnityEngine.UIElements;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEditor.Graphing.Util;
using UnityEditor.Graphing;
using UnityEditor.ShaderGraph.Internal;
using System.Collections.Generic;
namespace UnityEditor.ShaderGraph.Drawing.Inspector
{
public static class PropertyDrawerUtils
{
public static Label CreateLabel(string text, int indentLevel = 0, FontStyle fontStyle = FontStyle.Normal)
{
string label = new string(' ', indentLevel * 4);
var labelVisualElement = new Label(label + text);
labelVisualElement.style.unityFontStyleAndWeight = fontStyle;
labelVisualElement.name = "header";
return labelVisualElement;
}
public static Label CreateLabel(string text, int indentLevel = 0)
{
string label = new string(' ', indentLevel * 4);
var labelVisualElement = new Label(label + text);
return labelVisualElement;
}
internal enum UIPrecisionForShaderGraphs
{
Inherit = Precision.Inherit,
Single = Precision.Single,
Half = Precision.Half,
UseGraphPrecision = Precision.Graph,
}
internal static void AddDefaultNodeProperties(VisualElement parentElement, AbstractMaterialNode node, Action setNodesAsDirtyCallback, Action updateNodeViewsCallback)
{
EnumField precisionField = null;
if (node.canSetPrecision)
{
precisionField = new EnumField((UIPrecisionForShaderGraphs)node.precision);
var propertyRow = new PropertyRow(new Label("Precision"));
propertyRow.Add(precisionField, (field) =>
{
field.RegisterValueChangedCallback(evt =>
{
if (evt.newValue.Equals(node.precision))
return;
setNodesAsDirtyCallback?.Invoke();
node.owner.owner.RegisterCompleteObjectUndo("Change precision");
node.precision = (Precision)evt.newValue;
node.owner.ValidateGraph();
updateNodeViewsCallback?.Invoke();
node.Dirty(ModificationScope.Graph);
});
});
if (node is Serialization.MultiJsonInternal.UnknownNodeType)
precisionField.SetEnabled(false);
parentElement.Add(propertyRow);
}
EnumField previewField = null;
if (node.hasPreview)
{
previewField = new EnumField(node.m_PreviewMode);
var propertyRow = new PropertyRow(new Label("Preview"));
propertyRow.Add(previewField, (field) =>
{
field.RegisterValueChangedCallback(evt =>
{
if (evt.newValue.Equals(node.m_PreviewMode))
return;
setNodesAsDirtyCallback?.Invoke();
node.owner.owner.RegisterCompleteObjectUndo("Change preview");
node.m_PreviewMode = (PreviewMode)evt.newValue;
updateNodeViewsCallback?.Invoke();
node.Dirty(ModificationScope.Graph);
});
});
if (node is Serialization.MultiJsonInternal.UnknownNodeType)
previewField.SetEnabled(false);
parentElement.Add(propertyRow);
}
if (node is BlockNode bnode)
{
AddCustomInterpolatorProperties(parentElement, bnode, setNodesAsDirtyCallback, updateNodeViewsCallback);
}
if (node is CustomInterpolatorNode cinode)
{
if (cinode.e_targetBlockNode != null)
{
AddCustomInterpolatorProperties(parentElement, cinode.e_targetBlockNode, setNodesAsDirtyCallback, updateNodeViewsCallback);
}
}
}
internal static void AddCustomInterpolatorProperties(VisualElement parentElement, BlockNode node, Action setNodesAsDirtyCallback, Action updateNodeViewsCallback)
{
if (!node.isCustomBlock)
return;
TextField textField = null;
{
textField = new TextField { value = node.customName, multiline = false };
var propertyRow = new PropertyRow(new Label("Name"));
propertyRow.Add(textField, (field) =>
{
field.RegisterCallback<FocusOutEvent>(evt =>
{
if (field.value.Equals(node.customName))
return;
HashSet<string> usedNames = new HashSet<string>();
foreach (var other in node.contextData.blocks)
if (other != node)
usedNames.Add(other.value.descriptor.displayName);
setNodesAsDirtyCallback?.Invoke();
node.owner.owner.RegisterCompleteObjectUndo("Change Block Name");
field.value = node.customName = GraphUtil.SanitizeName(usedNames, "{0}_{1}", NodeUtils.ConvertToValidHLSLIdentifier(field.value));
updateNodeViewsCallback?.Invoke();
node.Dirty(ModificationScope.Node);
node.owner?.ValidateGraph();
});
});
parentElement.Add(propertyRow);
}
EnumField typeField = null;
{
typeField = new EnumField(node.customWidth);
var propertyRow = new PropertyRow(new Label("Type"));
propertyRow.Add(typeField, (field) =>
{
field.RegisterValueChangedCallback(evt =>
{
if (evt.newValue.Equals(node.customWidth))
return;
setNodesAsDirtyCallback?.Invoke();
node.owner.owner.RegisterCompleteObjectUndo("Change Block Type");
node.customWidth = (BlockNode.CustomBlockType)evt.newValue;
updateNodeViewsCallback?.Invoke();
node.Dirty(ModificationScope.Topological);
node.owner?.ValidateGraph();
});
});
parentElement.Add(propertyRow);
}
}
internal static void AddCustomCheckboxProperty(
VisualElement parentElement, AbstractMaterialNode node,
Action setNodesAsDirtyCallback, Action updateNodeViewsCallback,
String label,
String undoLabel,
Func<bool> GetterFn,
Action<bool> SetterFn)
{
var fieldObj = new Toggle();
fieldObj.value = GetterFn();
var propertyRow = new PropertyRow(new Label(label));
propertyRow.Add(fieldObj, (field) =>
{
field.RegisterValueChangedCallback(evt =>
{
if (evt.newValue.Equals(GetterFn()))
return;
setNodesAsDirtyCallback?.Invoke();
node.owner.owner.RegisterCompleteObjectUndo("Change Disable Global Mip Bias");
SetterFn((bool)evt.newValue);
node.owner.ValidateGraph();
updateNodeViewsCallback?.Invoke();
node.Dirty(ModificationScope.Graph);
});
});
if (node is Serialization.MultiJsonInternal.UnknownNodeType)
fieldObj.SetEnabled(false);
parentElement.Add(propertyRow);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bee8af59ecde4f88b12e15e45df7704c
timeCreated: 1587016996

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8488c4add4764941a2d903bf014329b9
timeCreated: 1588090481

View file

@ -0,0 +1,86 @@
using System;
using System.Reflection;
using UnityEditor.Graphing;
using UnityEditor.ShaderGraph;
using UnityEditor.ShaderGraph.Drawing;
using UnityEditor.ShaderGraph.Drawing.Inspector;
using UnityEditor.ShaderGraph.Internal;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using UnityEditor.Graphing.Util;
using UnityEngine;
namespace UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers
{
internal interface IGetNodePropertyDrawerPropertyData
{
void GetPropertyData(Action setNodesAsDirtyCallback, Action updateNodeViewsCallback);
}
[SGPropertyDrawer(typeof(AbstractMaterialNode))]
public class AbstractMaterialNodePropertyDrawer : IPropertyDrawer, IGetNodePropertyDrawerPropertyData
{
public Action inspectorUpdateDelegate { get; set; }
Action m_setNodesAsDirtyCallback;
Action m_updateNodeViewsCallback;
public void GetPropertyData(Action setNodesAsDirtyCallback, Action updateNodeViewsCallback)
{
m_setNodesAsDirtyCallback = setNodesAsDirtyCallback;
m_updateNodeViewsCallback = updateNodeViewsCallback;
}
internal virtual void AddCustomNodeProperties(VisualElement parentElement, AbstractMaterialNode node, Action setNodesAsDirtyCallback, Action updateNodeViewsCallback)
{
}
VisualElement CreateGUI(AbstractMaterialNode node, InspectableAttribute attribute, out VisualElement propertyVisualElement)
{
VisualElement nodeSettings = new VisualElement();
var nameLabel = PropertyDrawerUtils.CreateLabel($"{node.name} Node", 0, FontStyle.Bold);
nodeSettings.Add(nameLabel);
if (node.sgVersion < node.latestVersion)
{
string deprecationText = null;
string buttonText = null;
string labelText = null;
MessageType messageType = MessageType.Warning;
if (node is IHasCustomDeprecationMessage nodeWithCustomDeprecationSettings)
{
nodeWithCustomDeprecationSettings.GetCustomDeprecationMessage(out deprecationText, out buttonText, out labelText, out messageType);
}
var help = HelpBoxRow.TryGetDeprecatedHelpBoxRow($"{node.name} Node", () =>
{
m_setNodesAsDirtyCallback?.Invoke();
node.owner.owner.RegisterCompleteObjectUndo($"Update {node.name} Node");
node.ChangeVersion(node.latestVersion);
inspectorUpdateDelegate?.Invoke();
m_updateNodeViewsCallback?.Invoke();
node.Dirty(ModificationScope.Graph);
}, deprecationText, buttonText, labelText, messageType);
if (help != null)
{
nodeSettings.Insert(0, help);
}
}
PropertyDrawerUtils.AddDefaultNodeProperties(nodeSettings, node, m_setNodesAsDirtyCallback, m_updateNodeViewsCallback);
AddCustomNodeProperties(nodeSettings, node, m_setNodesAsDirtyCallback, m_updateNodeViewsCallback);
propertyVisualElement = null;
return nodeSettings;
}
public VisualElement DrawProperty(PropertyInfo propertyInfo, object actualObject, InspectableAttribute attribute)
{
return this.CreateGUI(
(AbstractMaterialNode)actualObject,
attribute,
out var propertyVisualElement);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6436de6b57634b32a3c95d37c1b9b2c0
timeCreated: 1588712275

View file

@ -0,0 +1,55 @@
using System;
using System.Reflection;
using UnityEditor.Graphing.Util;
using UnityEditor.ShaderGraph.Drawing;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers
{
[SGPropertyDrawer(typeof(bool))]
class BoolPropertyDrawer : IPropertyDrawer
{
internal delegate void ValueChangedCallback(bool newValue);
internal VisualElement CreateGUI(
ValueChangedCallback valueChangedCallback,
bool fieldToDraw,
string labelName,
out VisualElement propertyToggle,
int indentLevel = 0)
{
var row = new PropertyRow(PropertyDrawerUtils.CreateLabel(labelName, indentLevel));
// Create and assign toggle as out variable here so that callers can also do additional work with enabling/disabling if needed
propertyToggle = new Toggle();
row.Add((Toggle)propertyToggle, (toggle) =>
{
toggle.value = fieldToDraw;
});
if (valueChangedCallback != null)
{
var toggle = (Toggle)propertyToggle;
toggle.OnToggleChanged(evt => valueChangedCallback(evt.newValue));
}
row.styleSheets.Add(Resources.Load<StyleSheet>("Styles/PropertyRow"));
return row;
}
public Action inspectorUpdateDelegate { get; set; }
public VisualElement DrawProperty(
PropertyInfo propertyInfo,
object actualObject,
InspectableAttribute attribute)
{
return this.CreateGUI(
// Use the setter from the provided property as the callback
newBoolValue => propertyInfo.GetSetMethod(true).Invoke(actualObject, new object[] { newBoolValue }),
(bool)propertyInfo.GetValue(actualObject),
attribute.labelName,
out var propertyVisualElement);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c6b03af40f8944e4a3c303c7875029d0
timeCreated: 1588094617

Some files were not shown because too many files have changed in this diff Show more