CustomNodeGraph.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. {
  2. FreePascal / Delphi bindings for ImGui
  3. Copyright (C) 2023 Coldzer0 <Coldzer0 [at] protonmail.ch>
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the MIT License.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. MIT License for more details.
  10. }
  11. // based on https://gist.github.com/ocornut/7e9b3ec566a333d725d4
  12. Unit CustomNodeGraph;
  13. {$IfDef FPC}
  14. {$mode Delphi}{$H+}
  15. {$ModeSwitch advancedrecords}
  16. {$EndIf}
  17. Interface
  18. Uses
  19. Generics.Collections,
  20. SysUtils,
  21. PasImGui,
  22. PasImGui.Utils;
  23. Type
  24. { TNode }
  25. TNode = Record
  26. ID: Integer;
  27. Name: Array [0..31] Of AnsiChar;
  28. Pos, Size: ImVec2;
  29. Value: Single;
  30. Color: ImVec4;
  31. InputsCount, OutputsCount: Integer;
  32. Function GetInputSlotPos(slotNo: Integer): ImVec2;
  33. Function GetOutputSlotPos(slotNo: Integer): ImVec2;
  34. Constructor Create(id_: Integer; Const Name_: PAnsiChar;
  35. pos_: ImVec2; Value_: Single; color_: ImVec4; inputsCount_, outputsCount_: Integer);
  36. End;
  37. { TNodeLink }
  38. TNodeLink = Record
  39. InputIdx, InputSlot, OutputIdx, OutputSlot: Integer;
  40. Constructor Create(inputIdx_, inputSlot_, outputIdx_, outputSlot_: Integer);
  41. End;
  42. Procedure ShowExampleAppCustomNodeGraph(opened: PBoolean);
  43. Implementation
  44. Uses
  45. Math;
  46. Var
  47. nodes: TList<TNode>;
  48. links: TList<TNodeLink>;
  49. scrolling: ImVec2 = (x: 0.0; y: 0.0);
  50. inited: Boolean = False;
  51. show_grid: Boolean = False;
  52. node_selected: Integer = -1;
  53. test : Single = 0.0;
  54. Procedure ShowExampleAppCustomNodeGraph(opened: PBoolean);
  55. Var
  56. io: PImGuiIO;
  57. node, node_inp, node_out: TNode;
  58. link: TNodeLink;
  59. node_idx, slot_idx, link_idx: Integer;
  60. offset, win_pos, canvas_sz, p1, p2, node_rect_min, node_rect_max, scene_pos: ImVec2;
  61. draw_list: PImDrawList;
  62. GRID_COLOR, node_bg_color: ImU32;
  63. GRID_SZ, x, y: Single;
  64. old_any_active, node_widgets_active, node_moving_active: Boolean;
  65. open_context_menu: Boolean;
  66. node_hovered_in_list: Integer;
  67. node_hovered_in_scene: Integer;
  68. Const
  69. NODE_SLOT_RADIUS: Single = 4.0;
  70. NODE_WINDOW_PADDING: ImVec2 = (x: 8.0; y: 8.0);
  71. Begin
  72. ImGui.SetNextWindowSize(ImVec2.New(800, 600), ImGuiCond_FirstUseEver);
  73. //ImGui.SetNextWindowPosCenter(ImGuiCond_FirstUseEver);
  74. If Not ImGui.Begin_('Example: Custom Node Graph', opened) Then
  75. Begin
  76. ImGui.End_; // Early out if the window is collapsed, as an optimization.
  77. exit;
  78. End;
  79. io := ImGui.GetIO();
  80. If Not inited Then
  81. Begin
  82. nodes := TList<TNode>.Create();
  83. links := TList<TNodeLink>.Create();
  84. nodes.Add(TNode.Create(0, 'MainTex', ImVec2.New(40, 50), 0.5, ImVec4.New(255, 100, 100), 1, 1));
  85. nodes.Add(TNode.Create(1, 'BumpMap', ImVec2.New(40, 150), 0.42, ImVec4.New(200, 100, 200), 1, 1));
  86. nodes.Add(TNode.Create(2, 'Combine', ImVec2.New(270, 80), 1.0, ImVec4.New(0, 200, 100), 2, 2));
  87. links.Add(TNodeLink.Create(0, 0, 2, 0));
  88. links.Add(TNodeLink.Create(1, 0, 2, 1));
  89. inited := True;
  90. End;
  91. open_context_menu := False;
  92. node_hovered_in_list := -1;
  93. node_hovered_in_scene := -1;
  94. // Draw a list of nodes on the left side
  95. ImGui.BeginChild('node_list', ImVec2.New(100, 0));
  96. ImGui.Text('Nodes');
  97. ImGui.Separator();
  98. For node_idx := 0 To Pred(nodes.Count) Do
  99. Begin
  100. node := nodes[node_idx];
  101. ImGui.PushIdInt(node.ID);
  102. If ImGui.Selectable(node.Name, node.ID = node_selected) Then
  103. node_selected := node.ID;
  104. If ImGui.IsItemHovered() Then
  105. Begin
  106. node_hovered_in_list := node.ID;
  107. open_context_menu := Boolean(Ord(open_context_menu) Or
  108. Ord(ImGui.IsMouseClicked(ImGuiMouseButton_Left)));
  109. End;
  110. ImGui.PopID();
  111. End;
  112. ImGui.EndChild();
  113. ImGui.SameLine();
  114. ImGui.BeginGroup();
  115. // Create our child canvas
  116. ImGui.Text('Hold middle mouse button to scroll (%.2f,%.2f)',[scrolling.x, scrolling.y]);
  117. ImGui.SameLine();
  118. ImGui.Checkbox('Show grid', @show_grid);
  119. ImGui.PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2.New(1, 1));
  120. ImGui.PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2.New(0, 0));
  121. ImGui.PushStyleColor(ImGuiCol_ChildBg, IM_COL32(60, 60, 70, 200));
  122. ImGui.BeginChild('scrolling_region', ImVec2.New(0, 0), ImGuiChildFlags_None,
  123. ImGuiWindowFlags_NoScrollbar Or ImGuiWindowFlags_NoMove);
  124. ImGui.PopStyleVar(); // WindowPadding
  125. ImGui.PushItemWidth(120.0);
  126. offset := ImGui.GetCursorScreenPos() + scrolling;
  127. draw_list := ImGui.GetWindowDrawList();
  128. // Display grid
  129. If show_grid Then
  130. Begin
  131. GRID_COLOR := IM_COL32(200, 200, 200, 40);
  132. GRID_SZ := 64.0;
  133. win_pos := ImGui.GetCursorScreenPos();
  134. canvas_sz := ImGui.GetWindowSize();
  135. x := FMod(scrolling.x, GRID_SZ);
  136. while x < canvas_sz.x do
  137. begin
  138. draw_list^.AddLine(ImVec2.New(x, 0.0) + win_pos, ImVec2.New(x, canvas_sz.y) + win_pos, GRID_COLOR);
  139. x := x + GRID_SZ;
  140. end;
  141. y := fmod(scrolling.y, GRID_SZ);
  142. While y < canvas_sz.y Do
  143. Begin
  144. draw_list^.AddLine(ImVec2.New(0.0, y) + win_pos, ImVec2.New(canvas_sz.x, y) + win_pos, GRID_COLOR);
  145. y := y + GRID_SZ;
  146. End;
  147. End;
  148. // Display links
  149. draw_list^.ChannelsSplit(2);
  150. draw_list^.ChannelsSetCurrent(0); // Background
  151. For link_idx := 0 To Pred(links.Count) Do
  152. Begin
  153. link := links[link_idx];
  154. node_inp := nodes[link.InputIdx];
  155. node_out := nodes[link.OutputIdx];
  156. p1 := offset + node_inp.GetOutputSlotPos(link.InputSlot);
  157. p2 := offset + node_out.GetInputSlotPos(link.OutputSlot);
  158. draw_list^.AddBezierCubic(p1, p1 + ImVec2.New(-50, 0), p2 + ImVec2.New(+50, 0), p2, IM_COL32(200, 200, 100, 255), 3.0);
  159. End;
  160. // Display nodes
  161. For node_idx := 0 To Pred(nodes.Count) Do
  162. Begin
  163. node := nodes[node_idx];
  164. ImGui.PushIdInt(node.ID);
  165. node_rect_min := offset + node.Pos;
  166. // Display node contents first
  167. draw_list^.ChannelsSetCurrent(1); // Foreground
  168. old_any_active := ImGui.IsAnyItemActive();
  169. ImGui.SetCursorScreenPos(node_rect_min + NODE_WINDOW_PADDING);
  170. ImGui.BeginGroup(); // Lock horizontal position
  171. ImGui.Text('%s', [node.Name]);
  172. ImGui.SliderFloat('##value', @node.Value, 0.0, 1.0, 'Alpha %.2f');
  173. ImGui.ColorEdit3('##color', @node.Color.x);
  174. ImGui.EndGroup();
  175. // Save the size of what we have emitted and whether any of the widgets are being used
  176. node_widgets_active := (Not old_any_active And ImGui.IsAnyItemActive());
  177. node.Size := ImGui.GetItemRectSize() + NODE_WINDOW_PADDING + NODE_WINDOW_PADDING;
  178. node_rect_max := node_rect_min + node.Size;
  179. // Display node box
  180. draw_list^.ChannelsSetCurrent(0); // Background
  181. ImGui.SetCursorScreenPos(node_rect_min);
  182. ImGui.InvisibleButton('node', node.Size);
  183. If ImGui.IsItemHovered() Then
  184. Begin
  185. node_hovered_in_scene := node.ID;
  186. open_context_menu := open_context_menu Or ImGui.IsMouseClicked(1);
  187. End;
  188. node_moving_active := ImGui.IsItemActive();
  189. If (node_widgets_active Or node_moving_active) Then
  190. node_selected := node.ID;
  191. If (node_moving_active And ImGui.IsMouseDragging(ImGuiMouseButton_Left)) Then
  192. node.Pos := node.Pos + io^.MouseDelta;
  193. If ((node_hovered_in_list = node.ID) Or (node_hovered_in_scene = node.ID) Or
  194. ((node_hovered_in_list = -1) And (node_selected = node.ID))) Then
  195. node_bg_color := IM_COL32(75, 75, 75, 255)
  196. Else
  197. node_bg_color := IM_COL32(60, 60, 60, 255);
  198. draw_list^.AddRectFilled(node_rect_min, node_rect_max, node_bg_color, 4.0);
  199. draw_list^.AddRect(node_rect_min, node_rect_max, IM_COL32(100, 100, 100, 255), 4.0);
  200. For slot_idx := 0 To Pred(node.InputsCount) Do
  201. draw_list^.AddCircleFilled(offset + node.GetInputSlotPos(slot_idx),
  202. NODE_SLOT_RADIUS, IM_COL32(150, 150, 150, 150));
  203. For slot_idx := 0 To Pred(node.OutputsCount) Do
  204. draw_list^.AddCircleFilled(offset + node.GetOutputSlotPos(slot_idx),
  205. NODE_SLOT_RADIUS, IM_COL32(150, 150, 150, 150));
  206. ImGui.PopID();
  207. nodes[node_idx] := node;
  208. End;
  209. draw_list^.ChannelsMerge();
  210. // Open context menu
  211. If ImGui.IsMouseReleased(ImGuiMouseButton_Right) Then
  212. Begin
  213. If ImGui.IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup) Or
  214. (Not ImGui.IsAnyItemHovered()) Then
  215. Begin
  216. node_hovered_in_list := -1;
  217. node_hovered_in_scene := -1;
  218. node_selected := -1;
  219. open_context_menu := True;
  220. End;
  221. End;
  222. If open_context_menu Then
  223. Begin
  224. ImGui.OpenPopup('context_menu');
  225. If (node_hovered_in_list <> -1) Then
  226. node_selected := node_hovered_in_list;
  227. If (node_hovered_in_scene <> -1) Then
  228. node_selected := node_hovered_in_scene;
  229. End;
  230. // Draw context menu
  231. ImGui.PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2.New(8, 8));
  232. If ImGui.BeginPopup('context_menu') Then
  233. Begin
  234. scene_pos := ImGui.GetMousePosOnOpeningCurrentPopup() - offset;
  235. If node_selected <> -1 Then
  236. Begin
  237. node := nodes[node_selected];
  238. ImGui.Text('Node "%s"', [node.Name]);
  239. ImGui.Separator();
  240. If ImGui.MenuItem('Rename..', nil, False, False) Then
  241. Begin
  242. End;
  243. If ImGui.MenuItem('Delete', nil, False, False) Then
  244. Begin
  245. End;
  246. If ImGui.MenuItem('Copy', nil, False, False) Then
  247. Begin
  248. End;
  249. End
  250. Else
  251. Begin
  252. If ImGui.MenuItem('Add') Then
  253. nodes.Add(Node.Create(nodes.Count, 'New node', scene_pos, 0.5,
  254. ImVec4.New(100, 100, 200), 2, 2));
  255. If ImGui.MenuItem('Past', nil, False, False) Then
  256. Begin
  257. End;
  258. End;
  259. ImGui.EndPopup();
  260. End;
  261. ImGui.PopStyleVar();
  262. // Scrolling
  263. If (ImGui.IsWindowHovered() And (Not ImGui.IsAnyItemActive()) And
  264. ImGui.IsMouseDragging(ImGuiMouseButton_Middle, 0.0)) Then
  265. scrolling := scrolling + io.MouseDelta;
  266. ImGui.PopItemWidth();
  267. ImGui.EndChild();
  268. ImGui.PopStyleColor();
  269. ImGui.PopStyleVar();
  270. ImGui.EndGroup();
  271. ImGui.End_();
  272. End;
  273. { TNode }
  274. Function TNode.GetInputSlotPos(slotNo: Integer): ImVec2;
  275. Begin
  276. Result.x := Pos.x;
  277. Result.y := Pos.y + Size.y * Single(slotNo + 1) / (Single(InputsCount) + 1);
  278. End;
  279. Function TNode.GetOutputSlotPos(slotNo: Integer): ImVec2;
  280. Begin
  281. Result.x := Pos.x + Size.x;
  282. Result.y := Pos.y + Size.y * Single(slotNo + 1) / (Single(OutputsCount) + 1);
  283. End;
  284. Constructor TNode.Create(id_: Integer; Const Name_: PAnsiChar;
  285. pos_: ImVec2; Value_: Single; color_: ImVec4; inputsCount_, outputsCount_: Integer);
  286. Begin
  287. Self.ID := id_;
  288. StrLCopy(Self.Name, name_, SizeOf(Self.Name) - 1);
  289. Self.Pos := pos_;
  290. Self.Value := value_;
  291. Self.Color := color_;
  292. Self.InputsCount := inputsCount_;
  293. Self.OutputsCount := outputsCount_;
  294. End;
  295. { TNodeLink }
  296. Constructor TNodeLink.Create(inputIdx_, inputSlot_, outputIdx_, outputSlot_: Integer);
  297. Begin
  298. Self.InputIdx := inputIdx_;
  299. Self.InputSlot := inputSlot_;
  300. Self.OutputIdx := outputIdx_;
  301. Self.OutputSlot := outputSlot_;
  302. End;
  303. Initialization
  304. Finalization
  305. nodes.Free;
  306. links.Free;
  307. End.