NodeGraphViewModel.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. using System.Collections.ObjectModel;
  2. using System.Reflection;
  3. using Avalonia.Input;
  4. using PixiEditor.Models.Commands.Attributes.Commands;
  5. using PixiEditor.ChangeableDocument.Actions;
  6. using PixiEditor.ChangeableDocument.Actions.Generated;
  7. using PixiEditor.ChangeableDocument.Changeables.Graph;
  8. using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
  9. using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
  10. using PixiEditor.ChangeableDocument.ChangeInfos;
  11. using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
  12. using PixiEditor.Models.DocumentModels;
  13. using PixiEditor.Models.Handlers;
  14. using Drawie.Numerics;
  15. using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
  16. using PixiEditor.ViewModels.Nodes;
  17. namespace PixiEditor.ViewModels.Document;
  18. internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposable
  19. {
  20. public DocumentViewModel DocumentViewModel { get; }
  21. public ObservableCollection<INodeHandler> AllNodes { get; } = new();
  22. public ObservableCollection<NodeConnectionViewModel> Connections { get; } = new();
  23. public ObservableCollection<NodeFrameViewModelBase> Frames { get; } = new();
  24. public ObservableCollection<string> AvailableRenderOutputs { get; } = new();
  25. public StructureTree StructureTree { get; } = new();
  26. public INodeHandler? OutputNode { get; private set; }
  27. public Dictionary<string, INodeHandler> CustomRenderOutputs { get; } = new();
  28. public Dictionary<Guid, INodeHandler> NodeLookup { get; } = new();
  29. IReadOnlyDictionary<Guid, INodeHandler> INodeGraphHandler.NodeLookup => NodeLookup;
  30. private DocumentInternalParts Internals { get; }
  31. public NodeGraphViewModel(DocumentViewModel documentViewModel, DocumentInternalParts internals)
  32. {
  33. DocumentViewModel = documentViewModel;
  34. Internals = internals;
  35. }
  36. public void AddNode(INodeHandler node)
  37. {
  38. if (OutputNode == null &&
  39. node.InternalName == typeof(OutputNode).GetCustomAttribute<NodeInfoAttribute>().UniqueName)
  40. {
  41. OutputNode = node;
  42. }
  43. AllNodes.Add(node);
  44. StructureTree.Update(this);
  45. NodeLookup[node.Id] = node;
  46. UpdateAvailableRenderOutputs();
  47. }
  48. public void RemoveNode(Guid nodeId)
  49. {
  50. var node = AllNodes.FirstOrDefault(x => x.Id == nodeId);
  51. if (node != null)
  52. {
  53. AllNodes.Remove(node);
  54. }
  55. StructureTree.Update(this);
  56. NodeLookup.Remove(nodeId);
  57. UpdateAvailableRenderOutputs();
  58. }
  59. public void AddFrame(Guid frameId, IEnumerable<Guid> nodes)
  60. {
  61. var frame = new NodeFrameViewModel(frameId, AllNodes.Where(x => nodes.Contains(x.Id)));
  62. Frames.Add(frame);
  63. }
  64. public void AddZone(Guid frameId, string internalName, Guid startId, Guid endId)
  65. {
  66. var start = AllNodes.First(x => x.Id == startId);
  67. var end = AllNodes.First(x => x.Id == endId);
  68. var zone = new NodeZoneViewModel(frameId, internalName, start, end);
  69. Frames.Add(zone);
  70. }
  71. public void RemoveFrame(Guid guid)
  72. {
  73. var frame = Frames.FirstOrDefault(x => x.Id == guid);
  74. if (frame == null) return;
  75. Frames.Remove(frame);
  76. }
  77. public void SetConnection(NodeConnectionViewModel connection)
  78. {
  79. var existingInputConnection = Connections.FirstOrDefault(x => x.InputProperty == connection.InputProperty);
  80. if (existingInputConnection != null)
  81. {
  82. Connections.Remove(existingInputConnection);
  83. existingInputConnection.InputProperty.ConnectedOutput = null;
  84. existingInputConnection.OutputProperty.ConnectedInputs.Remove(existingInputConnection.InputProperty);
  85. }
  86. connection.InputProperty.ConnectedOutput = connection.OutputProperty;
  87. connection.OutputProperty.ConnectedInputs.Add(connection.InputProperty);
  88. Connections.Add(connection);
  89. StructureTree.Update(this);
  90. }
  91. public void RemoveConnection(Guid nodeId, string property)
  92. {
  93. var connection = Connections.FirstOrDefault(x =>
  94. x.InputProperty.Node.Id == nodeId && x.InputProperty.PropertyName == property);
  95. if (connection != null)
  96. {
  97. connection.InputProperty.ConnectedOutput = null;
  98. connection.OutputProperty.ConnectedInputs.Remove(connection.InputProperty);
  99. Connections.Remove(connection);
  100. }
  101. var node = AllNodes.FirstOrDefault(x => x.Id == nodeId);
  102. if (node != null)
  103. {
  104. var input = node.Inputs.FirstOrDefault(x => x.PropertyName == property);
  105. if (input != null)
  106. {
  107. input.ConnectedOutput = null;
  108. }
  109. }
  110. StructureTree.Update(this);
  111. }
  112. public void RemoveConnections(Guid nodeId)
  113. {
  114. var connections = Connections
  115. .Where(x => x.InputProperty.Node.Id == nodeId || x.OutputProperty.Node.Id == nodeId).ToList();
  116. foreach (var connection in connections)
  117. {
  118. connection.InputProperty.ConnectedOutput = null;
  119. connection.OutputProperty.ConnectedInputs.Remove(connection.InputProperty);
  120. Connections.Remove(connection);
  121. }
  122. StructureTree.Update(this);
  123. }
  124. public bool TryTraverse(Func<INodeHandler, bool> func)
  125. {
  126. if (OutputNode == null) return false;
  127. var queue = CalculateExecutionQueue(OutputNode);
  128. while (queue.Count > 0)
  129. {
  130. var node = queue.Dequeue();
  131. func(node);
  132. }
  133. return true;
  134. }
  135. private Queue<INodeHandler> CalculateExecutionQueue(INodeHandler outputNode)
  136. {
  137. var finalQueue = new HashSet<INodeHandler>();
  138. var queueNodes = new Queue<INodeHandler>();
  139. queueNodes.Enqueue(outputNode);
  140. while (queueNodes.Count > 0)
  141. {
  142. var node = queueNodes.Dequeue();
  143. if (finalQueue.Contains(node))
  144. {
  145. continue;
  146. }
  147. bool canAdd = true;
  148. foreach (var input in node.Inputs)
  149. {
  150. if (input.ConnectedOutput == null)
  151. {
  152. continue;
  153. }
  154. if (finalQueue.Contains(input.ConnectedOutput.Node))
  155. {
  156. continue;
  157. }
  158. canAdd = false;
  159. if (finalQueue.Contains(input.ConnectedOutput.Node))
  160. {
  161. finalQueue.Remove(input.ConnectedOutput.Node);
  162. finalQueue.Add(input.ConnectedOutput.Node);
  163. }
  164. if (!queueNodes.Contains(input.ConnectedOutput.Node))
  165. {
  166. queueNodes.Enqueue(input.ConnectedOutput.Node);
  167. }
  168. }
  169. if (canAdd)
  170. {
  171. finalQueue.Add(node);
  172. }
  173. else
  174. {
  175. queueNodes.Enqueue(node);
  176. }
  177. }
  178. return new Queue<INodeHandler>(finalQueue);
  179. }
  180. public void SetNodePositions(List<INodeHandler> node, VecD startPos)
  181. {
  182. Guid[] nodeIds = node.Select(x => x.Id).ToArray();
  183. Internals.ActionAccumulator.AddActions(new NodePosition_Action(nodeIds, startPos));
  184. }
  185. public void UpdatePropertyValue(INodeHandler node, string property, object? value)
  186. {
  187. Internals.ActionAccumulator.AddFinishedActions(new UpdatePropertyValue_Action(node.Id, property, value));
  188. }
  189. public void RequestUpdateComputedPropertyValue(INodePropertyHandler property)
  190. {
  191. Internals.ActionAccumulator.AddActions(
  192. new GetComputedPropertyValue_Action(property.Node.Id, property.PropertyName, property.IsInput));
  193. }
  194. public T GetComputedPropertyValue<T>(INodePropertyHandler property)
  195. {
  196. var node = Internals.Tracker.Document.NodeGraph.AllNodes.FirstOrDefault(x => x.Id == property.Node.Id);
  197. if (property.IsInput)
  198. {
  199. var prop = node.GetInputProperty(property.PropertyName);
  200. if (prop == null) return default;
  201. return prop.Value is T value ? value : default;
  202. }
  203. var output = node.GetOutputProperty(property.PropertyName);
  204. if (output == null) return default;
  205. return output.Value is T outputValue ? outputValue : default;
  206. }
  207. public void EndChangeNodePosition()
  208. {
  209. Internals.ActionAccumulator.AddFinishedActions(new EndNodePosition_Action());
  210. }
  211. public void CreateNode(Type nodeType, VecD pos = default)
  212. {
  213. IAction change;
  214. PairNodeAttribute? pairAttribute = nodeType.GetCustomAttribute<PairNodeAttribute>(true);
  215. List<IAction> changes = new();
  216. if (pairAttribute != null)
  217. {
  218. Guid startId = Guid.NewGuid();
  219. Guid endId = Guid.NewGuid();
  220. changes.Add(new CreateNodePair_Action(startId, endId, nodeType));
  221. if (pos != default)
  222. {
  223. changes.Add(new NodePosition_Action([startId], pos));
  224. changes.Add(new EndNodePosition_Action());
  225. changes.Add(new NodePosition_Action([endId], new VecD(pos.X + 400, pos.Y)));
  226. changes.Add(new EndNodePosition_Action());
  227. }
  228. }
  229. else
  230. {
  231. Guid nodeId = Guid.NewGuid();
  232. changes.Add(new CreateNode_Action(nodeType, nodeId, Guid.Empty));
  233. if (pos != default)
  234. {
  235. changes.Add(new NodePosition_Action([nodeId], pos));
  236. changes.Add(new EndNodePosition_Action());
  237. }
  238. }
  239. Internals.ActionAccumulator.AddFinishedActions(changes.ToArray());
  240. }
  241. public void RemoveNodes(Guid[] selectedNodes)
  242. {
  243. List<IAction> actions = new();
  244. for (int i = 0; i < selectedNodes.Length; i++)
  245. {
  246. actions.Add(new DeleteNode_Action(selectedNodes[i]));
  247. }
  248. Internals.ActionAccumulator.AddFinishedActions(actions.ToArray());
  249. }
  250. // TODO: Remove this
  251. public void CreateNodeFrameAroundEverything()
  252. {
  253. CreateNodeFrame(AllNodes);
  254. }
  255. public void CreateNodeFrame(IEnumerable<INodeHandler> nodes)
  256. {
  257. Internals.ActionAccumulator.AddFinishedActions(new CreateNodeFrame_Action(Guid.NewGuid(),
  258. nodes.Select(x => x.Id)));
  259. }
  260. public void ConnectProperties(INodePropertyHandler? start, INodePropertyHandler? end)
  261. {
  262. if (start == null && end == null) return;
  263. INodeHandler inputNode = null, outputNode = null;
  264. string inputProperty = null, outputProperty = null;
  265. var input = start?.IsInput == true ? start : end;
  266. var output = start?.IsInput == false ? start : end;
  267. if (input == null && output != null)
  268. {
  269. input = output.ConnectedInputs?.FirstOrDefault();
  270. output = null;
  271. }
  272. if (input != null)
  273. {
  274. inputNode = input.Node;
  275. inputProperty = input.PropertyName;
  276. }
  277. if (output != null)
  278. {
  279. outputNode = output.Node;
  280. outputProperty = output.PropertyName;
  281. }
  282. if (input == null) return;
  283. IAction action = input != null && output != null
  284. ? new ConnectProperties_Action(inputNode.Id, outputNode.Id, inputProperty, outputProperty)
  285. : new DisconnectProperty_Action(inputNode.Id, inputProperty);
  286. Internals.ActionAccumulator.AddFinishedActions(action);
  287. }
  288. public void UpdateAvailableRenderOutputs()
  289. {
  290. Dictionary<string, INodeHandler> outputs = new();
  291. foreach (var node in AllNodes)
  292. {
  293. if (node.InternalName == typeof(CustomOutputNode).GetCustomAttribute<NodeInfoAttribute>().UniqueName)
  294. {
  295. var nameInput =
  296. node.Inputs.FirstOrDefault(x => x.PropertyName == CustomOutputNode.OutputNamePropertyName);
  297. if (nameInput is { Value: string name } && !string.IsNullOrEmpty(name))
  298. {
  299. outputs[name] = node;
  300. }
  301. }
  302. else if (node.InternalName == typeof(OutputNode).GetCustomAttribute<NodeInfoAttribute>().UniqueName)
  303. {
  304. outputs["DEFAULT"] = node;
  305. }
  306. }
  307. RemoveExcessiveRenderOutputs(outputs);
  308. AddMissingRenderOutputs(outputs);
  309. }
  310. private void RemoveExcessiveRenderOutputs(Dictionary<string, INodeHandler> outputs)
  311. {
  312. for (int i = AvailableRenderOutputs.Count - 1; i >= 0; i--)
  313. {
  314. var outputName = AvailableRenderOutputs[i];
  315. if (!outputs.ContainsKey(outputName))
  316. {
  317. AvailableRenderOutputs.RemoveAt(i);
  318. }
  319. CustomRenderOutputs.Remove(outputName);
  320. }
  321. }
  322. private void AddMissingRenderOutputs(Dictionary<string, INodeHandler> outputs)
  323. {
  324. foreach (var output in outputs)
  325. {
  326. if (!AvailableRenderOutputs.Contains(output.Key))
  327. {
  328. AvailableRenderOutputs.Add(output.Key);
  329. }
  330. CustomRenderOutputs[output.Key] = output.Value;
  331. }
  332. }
  333. public void Dispose()
  334. {
  335. foreach (var node in AllNodes)
  336. {
  337. node.Dispose();
  338. }
  339. }
  340. }