Nessuna descrizione
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SGBlackboardField.cs 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using UnityEngine;
  6. using UnityEngine.UIElements;
  7. using UnityEditor.Graphing;
  8. using UnityEditor.Experimental.GraphView;
  9. using UnityEditor.ShaderGraph.Drawing.Controls;
  10. using UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers;
  11. using UnityEditor.ShaderGraph.Internal;
  12. using UnityEngine.Assertions;
  13. using ContextualMenuManipulator = UnityEngine.UIElements.ContextualMenuManipulator;
  14. using GraphDataStore = UnityEditor.ShaderGraph.DataStore<UnityEditor.ShaderGraph.GraphData>;
  15. namespace UnityEditor.ShaderGraph.Drawing
  16. {
  17. class SGBlackboardField : GraphElement, IInspectable, ISGControlledElement<ShaderInputViewController>, IDisposable
  18. {
  19. static readonly Texture2D k_ExposedIcon = Resources.Load<Texture2D>("GraphView/Nodes/BlackboardFieldExposed");
  20. static readonly string k_UxmlTemplatePath = "UXML/Blackboard/SGBlackboardField";
  21. static readonly string k_StyleSheetPath = "Styles/SGBlackboard";
  22. ShaderInputViewModel m_ViewModel;
  23. ShaderInputViewModel ViewModel
  24. {
  25. get => m_ViewModel;
  26. set => m_ViewModel = value;
  27. }
  28. VisualElement m_ContentItem;
  29. Pill m_Pill;
  30. Label m_TypeLabel;
  31. TextField m_TextField;
  32. internal TextField textField => m_TextField;
  33. Action m_ResetReferenceNameTrigger;
  34. List<Node> m_SelectedNodes = new List<Node>();
  35. public string text
  36. {
  37. get { return m_Pill.text; }
  38. set { m_Pill.text = value; }
  39. }
  40. public string typeText
  41. {
  42. get { return m_TypeLabel.text; }
  43. set { m_TypeLabel.text = value; }
  44. }
  45. public Texture icon
  46. {
  47. get { return m_Pill.icon; }
  48. set { m_Pill.icon = value; }
  49. }
  50. public bool highlighted
  51. {
  52. get { return m_Pill.highlighted; }
  53. set { m_Pill.highlighted = value; }
  54. }
  55. internal SGBlackboardField(ShaderInputViewModel viewModel)
  56. {
  57. ViewModel = viewModel;
  58. // Store ShaderInput in userData object
  59. userData = ViewModel.model;
  60. if (userData == null)
  61. {
  62. AssertHelpers.Fail("Could not initialize blackboard field as shader input was null.");
  63. return;
  64. }
  65. // Store the Model guid as viewDataKey as that is persistent
  66. viewDataKey = ViewModel.model.guid.ToString();
  67. var visualTreeAsset = Resources.Load<VisualTreeAsset>(k_UxmlTemplatePath);
  68. Assert.IsNotNull(visualTreeAsset);
  69. VisualElement mainContainer = visualTreeAsset.Instantiate();
  70. var styleSheet = Resources.Load<StyleSheet>(k_StyleSheetPath);
  71. Assert.IsNotNull(styleSheet);
  72. styleSheets.Add(styleSheet);
  73. mainContainer.AddToClassList("mainContainer");
  74. mainContainer.pickingMode = PickingMode.Ignore;
  75. m_ContentItem = mainContainer.Q("contentItem");
  76. m_Pill = mainContainer.Q<Pill>("pill");
  77. m_TypeLabel = mainContainer.Q<Label>("typeLabel");
  78. m_TextField = mainContainer.Q<TextField>("textField");
  79. m_TextField.style.display = DisplayStyle.None;
  80. // Handles the upgrade fix for the old color property deprecation
  81. if (shaderInput is AbstractShaderProperty property)
  82. {
  83. property.onAfterVersionChange += () =>
  84. {
  85. this.typeText = property.GetPropertyTypeString();
  86. this.m_InspectorUpdateDelegate();
  87. };
  88. }
  89. Add(mainContainer);
  90. RegisterCallback<MouseDownEvent>(OnMouseDownEvent);
  91. // setting Capabilities.Selectable adds a ClickSelector
  92. capabilities |= Capabilities.Selectable | Capabilities.Droppable | Capabilities.Deletable | Capabilities.Renamable;
  93. ClearClassList();
  94. AddToClassList("blackboardField");
  95. this.name = "SGBlackboardField";
  96. UpdateFromViewModel();
  97. // add the right click context menu
  98. IManipulator contextMenuManipulator = new ContextualMenuManipulator(AddContextMenuOptions);
  99. this.AddManipulator(contextMenuManipulator);
  100. this.AddManipulator(new SelectionDropper());
  101. this.AddManipulator(new ContextualMenuManipulator(BuildFieldContextualMenu));
  102. // When a display name is changed through the BlackboardPill, bind this callback to handle it with appropriate change action
  103. var textInputElement = m_TextField.Q(TextField.textInputUssName);
  104. textInputElement.RegisterCallback<FocusOutEvent>(e => { OnEditTextFinished(); }, TrickleDown.TrickleDown);
  105. ShaderGraphPreferences.onAllowDeprecatedChanged += UpdateTypeText;
  106. RegisterCallback<MouseEnterEvent>(evt => OnMouseHover(evt, ViewModel.model));
  107. RegisterCallback<MouseLeaveEvent>(evt => OnMouseHover(evt, ViewModel.model));
  108. RegisterCallback<DragUpdatedEvent>(OnDragUpdatedEvent);
  109. var blackboard = ViewModel.parentView.GetFirstAncestorOfType<SGBlackboard>();
  110. if (blackboard != null)
  111. {
  112. // These callbacks are used for the property dragging scroll behavior
  113. RegisterCallback<DragEnterEvent>(blackboard.OnDragEnterEvent);
  114. RegisterCallback<DragExitedEvent>(blackboard.OnDragExitedEvent);
  115. // These callbacks are used for the property dragging scroll behavior
  116. RegisterCallback<DragEnterEvent>(blackboard.OnDragEnterEvent);
  117. RegisterCallback<DragExitedEvent>(blackboard.OnDragExitedEvent);
  118. }
  119. }
  120. void AddContextMenuOptions(ContextualMenuPopulateEvent evt)
  121. {
  122. // Checks if the reference name has been overridden and appends menu action to reset it, if so
  123. if (shaderInput.isRenamable &&
  124. !string.IsNullOrEmpty(shaderInput.overrideReferenceName))
  125. {
  126. evt.menu.AppendAction(
  127. "Reset Reference",
  128. e =>
  129. {
  130. var resetReferenceNameAction = new ResetReferenceNameAction();
  131. resetReferenceNameAction.shaderInputReference = shaderInput;
  132. ViewModel.requestModelChangeAction(resetReferenceNameAction);
  133. m_ResetReferenceNameTrigger();
  134. },
  135. DropdownMenuAction.AlwaysEnabled);
  136. }
  137. if (shaderInput is ColorShaderProperty colorProp)
  138. {
  139. PropertyNodeView.AddMainColorMenuOptions(evt, colorProp, controller.graphData, m_InspectorUpdateDelegate);
  140. }
  141. if (shaderInput is Texture2DShaderProperty texProp)
  142. {
  143. PropertyNodeView.AddMainTextureMenuOptions(evt, texProp, controller.graphData, m_InspectorUpdateDelegate);
  144. }
  145. }
  146. internal void UpdateFromViewModel()
  147. {
  148. this.text = ViewModel.inputName;
  149. this.icon = ViewModel.isInputExposed ? k_ExposedIcon : null;
  150. this.typeText = ViewModel.inputTypeName;
  151. }
  152. ShaderInputViewController m_Controller;
  153. // --- Begin ISGControlledElement implementation
  154. public void OnControllerChanged(ref SGControllerChangedEvent e)
  155. {
  156. }
  157. public void OnControllerEvent(SGControllerEvent e)
  158. {
  159. }
  160. public ShaderInputViewController controller
  161. {
  162. get => m_Controller;
  163. set
  164. {
  165. if (m_Controller != value)
  166. {
  167. if (m_Controller != null)
  168. {
  169. m_Controller.UnregisterHandler(this);
  170. }
  171. m_Controller = value;
  172. if (m_Controller != null)
  173. {
  174. m_Controller.RegisterHandler(this);
  175. }
  176. }
  177. }
  178. }
  179. SGController ISGControlledElement.controller => m_Controller;
  180. // --- ISGControlledElement implementation
  181. [Inspectable("Shader Input", null)]
  182. public ShaderInput shaderInput => ViewModel.model;
  183. public string inspectorTitle => ViewModel.inputName + " " + ViewModel.inputTypeName;
  184. public object GetObjectToInspect()
  185. {
  186. return shaderInput;
  187. }
  188. Action m_InspectorUpdateDelegate;
  189. public void SupplyDataToPropertyDrawer(IPropertyDrawer propertyDrawer, Action inspectorUpdateDelegate)
  190. {
  191. if (propertyDrawer is ShaderInputPropertyDrawer shaderInputPropertyDrawer)
  192. {
  193. // We currently need to do a halfway measure between the old way of handling stuff for property drawers (how FieldView and NodeView handle it)
  194. // 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.
  195. // We've maintained all the old callbacks as they are in the PropertyDrawer to reduce possible halo changes and support PropertyNodeView functionality
  196. // Instead we supply different underlying methods for the callbacks in the new SGBlackboardField,
  197. // that way both code paths should work until we can refactor PropertyNodeView
  198. shaderInputPropertyDrawer.GetViewModel(
  199. ViewModel,
  200. controller.graphData,
  201. ((triggerInspectorUpdate, modificationScope) =>
  202. {
  203. controller.DirtyNodes(modificationScope);
  204. if (triggerInspectorUpdate)
  205. inspectorUpdateDelegate();
  206. }));
  207. m_ResetReferenceNameTrigger = shaderInputPropertyDrawer.ResetReferenceName;
  208. m_InspectorUpdateDelegate = inspectorUpdateDelegate;
  209. }
  210. }
  211. void OnMouseDownEvent(MouseDownEvent e)
  212. {
  213. if ((e.clickCount == 2) && e.button == (int)MouseButton.LeftMouse && IsRenamable())
  214. {
  215. OpenTextEditor();
  216. e.StopPropagation();
  217. focusController.IgnoreEvent(e);
  218. }
  219. }
  220. void OnDragUpdatedEvent(DragUpdatedEvent evt)
  221. {
  222. if (m_SelectedNodes.Any())
  223. {
  224. foreach (var node in m_SelectedNodes)
  225. {
  226. node.RemoveFromClassList("hovered");
  227. }
  228. m_SelectedNodes.Clear();
  229. }
  230. }
  231. // TODO: Move to controller? Feels weird for this to be directly communicating with PropertyNodes etc.
  232. // 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
  233. void OnMouseHover(EventBase evt, ShaderInput input)
  234. {
  235. var graphView = ViewModel.parentView.GetFirstAncestorOfType<MaterialGraphView>();
  236. if (evt.eventTypeId == MouseEnterEvent.TypeId())
  237. {
  238. foreach (var node in graphView.nodes.ToList())
  239. {
  240. if (input is AbstractShaderProperty property)
  241. {
  242. if (node.userData is PropertyNode propertyNode)
  243. {
  244. if (propertyNode.property == input)
  245. {
  246. m_SelectedNodes.Add(node);
  247. node.AddToClassList("hovered");
  248. }
  249. }
  250. }
  251. else if (input is ShaderKeyword keyword)
  252. {
  253. if (node.userData is KeywordNode keywordNode)
  254. {
  255. if (keywordNode.keyword == input)
  256. {
  257. m_SelectedNodes.Add(node);
  258. node.AddToClassList("hovered");
  259. }
  260. }
  261. }
  262. else if (input is ShaderDropdown dropdown)
  263. {
  264. if (node.userData is DropdownNode dropdownNode)
  265. {
  266. if (dropdownNode.dropdown == input)
  267. {
  268. m_SelectedNodes.Add(node);
  269. node.AddToClassList("hovered");
  270. }
  271. }
  272. }
  273. }
  274. }
  275. else if (evt.eventTypeId == MouseLeaveEvent.TypeId() && m_SelectedNodes.Any())
  276. {
  277. foreach (var node in m_SelectedNodes)
  278. {
  279. node.RemoveFromClassList("hovered");
  280. }
  281. m_SelectedNodes.Clear();
  282. }
  283. }
  284. void UpdateTypeText()
  285. {
  286. if (shaderInput is AbstractShaderProperty asp)
  287. {
  288. typeText = asp.GetPropertyTypeString();
  289. }
  290. }
  291. internal void OpenTextEditor()
  292. {
  293. m_TextField.SetValueWithoutNotify(text);
  294. m_TextField.style.display = DisplayStyle.Flex;
  295. m_ContentItem.visible = false;
  296. m_TextField.Q(TextField.textInputUssName).Focus();
  297. m_TextField.SelectAll();
  298. }
  299. void OnEditTextFinished()
  300. {
  301. m_ContentItem.visible = true;
  302. m_TextField.style.display = DisplayStyle.None;
  303. if (text != m_TextField.text && String.IsNullOrWhiteSpace(m_TextField.text) == false && String.IsNullOrEmpty(m_TextField.text) == false)
  304. {
  305. var changeDisplayNameAction = new ChangeDisplayNameAction();
  306. changeDisplayNameAction.shaderInputReference = shaderInput;
  307. changeDisplayNameAction.newDisplayNameValue = m_TextField.text;
  308. ViewModel.requestModelChangeAction(changeDisplayNameAction);
  309. m_InspectorUpdateDelegate?.Invoke();
  310. }
  311. else
  312. {
  313. // Reset text field to original name
  314. m_TextField.value = text;
  315. }
  316. }
  317. protected virtual void BuildFieldContextualMenu(ContextualMenuPopulateEvent evt)
  318. {
  319. evt.menu.AppendAction("Rename", (a) => OpenTextEditor(), DropdownMenuAction.AlwaysEnabled);
  320. }
  321. public void Dispose()
  322. {
  323. m_ResetReferenceNameTrigger = null;
  324. m_InspectorUpdateDelegate = null;
  325. UnregisterCallback<MouseDownEvent>(OnMouseDownEvent);
  326. UnregisterCallback<MouseEnterEvent>(evt => OnMouseHover(evt, ViewModel.model));
  327. UnregisterCallback<MouseLeaveEvent>(evt => OnMouseHover(evt, ViewModel.model));
  328. UnregisterCallback<DragUpdatedEvent>(OnDragUpdatedEvent);
  329. var blackboard = ViewModel.parentView.GetFirstAncestorOfType<SGBlackboard>();
  330. UnregisterCallback<DragEnterEvent>(blackboard.OnDragEnterEvent);
  331. UnregisterCallback<DragExitedEvent>(blackboard.OnDragExitedEvent);
  332. ShaderGraphPreferences.onAllowDeprecatedChanged -= UpdateTypeText;
  333. // Clear references
  334. m_SelectedNodes = null;
  335. m_ContentItem = null;
  336. m_Pill = null;
  337. m_TypeLabel = null;
  338. m_TextField = null;
  339. m_Controller = null;
  340. m_ViewModel = null;
  341. userData = null;
  342. styleSheets.Clear();
  343. Clear();
  344. }
  345. }
  346. }