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,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