initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ca3677c253614a948be3cf8f0d17e254
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 956e673aa3cb4690b9c44d970ffd8e65
|
||||
timeCreated: 1611700923
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ce596a3c8ef34fc6bcd82c059ad53e57
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5e118fcc48ee4d78b7ea01a1d8011b1b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 04f0848d35cb46cabeec016d7ccd5169
|
||||
timeCreated: 1611267123
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7481cf2e22074dbdb915ebd086bd0652
|
||||
timeCreated: 1611611278
|
Loading…
Add table
Add a link
Reference in a new issue