NodeGraphViewModel.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. using System.Collections.ObjectModel;
  2. using System.Reflection;
  3. using Avalonia.Input;
  4. using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Commands;
  5. using PixiEditor.AvaloniaUI.Models.DocumentModels;
  6. using PixiEditor.AvaloniaUI.Models.Handlers;
  7. using PixiEditor.AvaloniaUI.ViewModels.Nodes;
  8. using PixiEditor.ChangeableDocument.Actions;
  9. using PixiEditor.ChangeableDocument.Actions.Generated;
  10. using PixiEditor.ChangeableDocument.Changeables.Graph;
  11. using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
  12. using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
  13. using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
  14. using PixiEditor.Numerics;
  15. namespace PixiEditor.AvaloniaUI.ViewModels.Document;
  16. internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
  17. {
  18. public DocumentViewModel DocumentViewModel { get; }
  19. public ObservableCollection<INodeHandler> AllNodes { get; } = new();
  20. public ObservableCollection<NodeConnectionViewModel> Connections { get; } = new();
  21. public ObservableCollection<NodeFrameViewModelBase> Frames { get; } = new();
  22. public StructureTree StructureTree { get; } = new();
  23. public INodeHandler? OutputNode { get; private set; }
  24. private DocumentInternalParts Internals { get; }
  25. public NodeGraphViewModel(DocumentViewModel documentViewModel, DocumentInternalParts internals)
  26. {
  27. DocumentViewModel = documentViewModel;
  28. Internals = internals;
  29. }
  30. public void AddNode(INodeHandler node)
  31. {
  32. if (OutputNode == null)
  33. {
  34. OutputNode = node; // TODO: this is not really correct yet, a way to check what node type is added is needed
  35. }
  36. AllNodes.Add(node);
  37. StructureTree.Update(this);
  38. }
  39. public void RemoveNode(Guid nodeId)
  40. {
  41. var node = AllNodes.FirstOrDefault(x => x.Id == nodeId);
  42. if (node != null)
  43. {
  44. AllNodes.Remove(node);
  45. }
  46. StructureTree.Update(this);
  47. }
  48. public void AddFrame(Guid frameId, IEnumerable<Guid> nodes)
  49. {
  50. var frame = new NodeFrameViewModel(frameId, AllNodes.Where(x => nodes.Contains(x.Id)));
  51. Frames.Add(frame);
  52. }
  53. public void AddZone(Guid frameId, string internalName, Guid startId, Guid endId)
  54. {
  55. var start = AllNodes.First(x => x.Id == startId);
  56. var end = AllNodes.First(x => x.Id == endId);
  57. var zone = new NodeZoneViewModel(frameId, internalName, start, end);
  58. Frames.Add(zone);
  59. }
  60. public void RemoveFrame(Guid guid)
  61. {
  62. var frame = Frames.FirstOrDefault(x => x.Id == guid);
  63. if (frame == null) return;
  64. Frames.Remove(frame);
  65. }
  66. public void SetConnection(NodeConnectionViewModel connection)
  67. {
  68. var existingInputConnection = Connections.FirstOrDefault(x => x.InputProperty == connection.InputProperty);
  69. if (existingInputConnection != null)
  70. {
  71. Connections.Remove(existingInputConnection);
  72. existingInputConnection.InputProperty.ConnectedOutput = null;
  73. existingInputConnection.OutputProperty.ConnectedInputs.Remove(existingInputConnection.InputProperty);
  74. }
  75. connection.InputProperty.ConnectedOutput = connection.OutputProperty;
  76. connection.OutputProperty.ConnectedInputs.Add(connection.InputProperty);
  77. Connections.Add(connection);
  78. StructureTree.Update(this);
  79. }
  80. public void RemoveConnection(Guid nodeId, string property)
  81. {
  82. var connection = Connections.FirstOrDefault(x =>
  83. x.InputProperty.Node.Id == nodeId && x.InputProperty.PropertyName == property);
  84. if (connection != null)
  85. {
  86. connection.InputProperty.ConnectedOutput = null;
  87. connection.OutputProperty.ConnectedInputs.Remove(connection.InputProperty);
  88. Connections.Remove(connection);
  89. }
  90. StructureTree.Update(this);
  91. }
  92. public void RemoveConnections(Guid nodeId)
  93. {
  94. var connections = Connections
  95. .Where(x => x.InputProperty.Node.Id == nodeId || x.OutputProperty.Node.Id == nodeId).ToList();
  96. foreach (var connection in connections)
  97. {
  98. connection.InputProperty.ConnectedOutput = null;
  99. connection.OutputProperty.ConnectedInputs.Remove(connection.InputProperty);
  100. Connections.Remove(connection);
  101. }
  102. StructureTree.Update(this);
  103. }
  104. public bool TryTraverse(Func<INodeHandler, bool> func)
  105. {
  106. if (OutputNode == null) return false;
  107. var queue = CalculateExecutionQueue(OutputNode);
  108. while (queue.Count > 0)
  109. {
  110. var node = queue.Dequeue();
  111. func(node);
  112. }
  113. return true;
  114. }
  115. private Queue<INodeHandler> CalculateExecutionQueue(INodeHandler outputNode)
  116. {
  117. // backwards breadth-first search
  118. var visited = new HashSet<INodeHandler>();
  119. var queueNodes = new Queue<INodeHandler>();
  120. List<INodeHandler> finalQueue = new();
  121. queueNodes.Enqueue(outputNode);
  122. while (queueNodes.Count > 0)
  123. {
  124. var node = queueNodes.Dequeue();
  125. if (!visited.Add(node))
  126. {
  127. continue;
  128. }
  129. finalQueue.Add(node);
  130. foreach (var input in node.Inputs)
  131. {
  132. if (input.ConnectedOutput == null)
  133. {
  134. continue;
  135. }
  136. queueNodes.Enqueue(input.ConnectedOutput.Node);
  137. }
  138. }
  139. finalQueue.Reverse();
  140. return new Queue<INodeHandler>(finalQueue);
  141. }
  142. public void SetNodePosition(INodeHandler node, VecD newPos)
  143. {
  144. Internals.ActionAccumulator.AddActions(new NodePosition_Action(node.Id, newPos));
  145. }
  146. public void UpdatePropertyValue(INodeHandler node, string property, object? value)
  147. {
  148. Internals.ActionAccumulator.AddFinishedActions(new UpdatePropertyValue_Action(node.Id, property, value));
  149. }
  150. public void EndChangeNodePosition()
  151. {
  152. Internals.ActionAccumulator.AddFinishedActions(new EndNodePosition_Action());
  153. }
  154. public void CreateNode(Type nodeType)
  155. {
  156. IAction change;
  157. PairNodeAttribute? pairAttribute = nodeType.GetCustomAttribute<PairNodeAttribute>(true);
  158. if (pairAttribute != null)
  159. {
  160. change = new CreateNodePair_Action(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), nodeType);
  161. }
  162. else
  163. {
  164. change = new CreateNode_Action(nodeType, Guid.NewGuid());
  165. }
  166. Internals.ActionAccumulator.AddFinishedActions(change);
  167. }
  168. public void RemoveNodes(Guid[] selectedNodes)
  169. {
  170. IAction[] actions = new IAction[selectedNodes.Length];
  171. for (int i = 0; i < selectedNodes.Length; i++)
  172. {
  173. actions[i] = new DeleteNode_Action(selectedNodes[i]);
  174. }
  175. Internals.ActionAccumulator.AddFinishedActions(actions);
  176. }
  177. // TODO: Remove this
  178. public void CreateNodeFrameAroundEverything()
  179. {
  180. CreateNodeFrame(AllNodes);
  181. }
  182. public void CreateNodeFrame(IEnumerable<INodeHandler> nodes)
  183. {
  184. Internals.ActionAccumulator.AddFinishedActions(new CreateNodeFrame_Action(Guid.NewGuid(),
  185. nodes.Select(x => x.Id)));
  186. }
  187. public void ConnectProperties(INodePropertyHandler? start, INodePropertyHandler? end)
  188. {
  189. if (start == null && end == null) return;
  190. INodeHandler inputNode = null, outputNode = null;
  191. string inputProperty = null, outputProperty = null;
  192. var input = start?.IsInput == true ? start : end;
  193. var output = start?.IsInput == false ? start : end;
  194. if (input == null && output != null)
  195. {
  196. input = output.ConnectedInputs.FirstOrDefault();
  197. output = null;
  198. }
  199. if (input != null)
  200. {
  201. inputNode = input.Node;
  202. inputProperty = input.PropertyName;
  203. }
  204. if (output != null)
  205. {
  206. outputNode = output.Node;
  207. outputProperty = output.PropertyName;
  208. }
  209. if (input == null) return;
  210. IAction action = input != null && output != null
  211. ? new ConnectProperties_Action(inputNode.Id, outputNode.Id, inputProperty, outputProperty)
  212. : new DisconnectProperty_Action(inputNode.Id, inputProperty);
  213. Internals.ActionAccumulator.AddFinishedActions(action);
  214. }
  215. }