imnodes.cpp 105 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123
  1. // the structure of this file:
  2. //
  3. // [SECTION] bezier curve helpers
  4. // [SECTION] draw list helper
  5. // [SECTION] ui state logic
  6. // [SECTION] render helpers
  7. // [SECTION] API implementation
  8. #include "imnodes.h"
  9. #include "imnodes_internal.h"
  10. #include <imgui.h>
  11. #define IMGUI_DEFINE_MATH_OPERATORS
  12. #include <imgui_internal.h>
  13. // Check minimum ImGui version
  14. #define MINIMUM_COMPATIBLE_IMGUI_VERSION 17400
  15. #if IMGUI_VERSION_NUM < MINIMUM_COMPATIBLE_IMGUI_VERSION
  16. #error "Minimum ImGui version requirement not met -- please use a newer version!"
  17. #endif
  18. #include <assert.h>
  19. #include <limits.h>
  20. #include <math.h>
  21. #include <new>
  22. #include <stdint.h>
  23. #include <stdio.h> // for fwrite, ssprintf, sscanf
  24. #include <stdlib.h>
  25. #include <string.h> // strlen, strncmp
  26. ImNodesContext* GImNodes = NULL;
  27. namespace ImNodes
  28. {
  29. namespace
  30. {
  31. // [SECTION] bezier curve helpers
  32. struct CubicBezier
  33. {
  34. ImVec2 P0, P1, P2, P3;
  35. int NumSegments;
  36. };
  37. inline ImVec2 EvalCubicBezier(
  38. const float t,
  39. const ImVec2& P0,
  40. const ImVec2& P1,
  41. const ImVec2& P2,
  42. const ImVec2& P3)
  43. {
  44. // B(t) = (1-t)**3 p0 + 3(1 - t)**2 t P1 + 3(1-t)t**2 P2 + t**3 P3
  45. const float u = 1.0f - t;
  46. const float b0 = u * u * u;
  47. const float b1 = 3 * u * u * t;
  48. const float b2 = 3 * u * t * t;
  49. const float b3 = t * t * t;
  50. return ImVec2(
  51. b0 * P0.x + b1 * P1.x + b2 * P2.x + b3 * P3.x,
  52. b0 * P0.y + b1 * P1.y + b2 * P2.y + b3 * P3.y);
  53. }
  54. // Calculates the closest point along each bezier curve segment.
  55. ImVec2 GetClosestPointOnCubicBezier(const int num_segments, const ImVec2& p, const CubicBezier& cb)
  56. {
  57. IM_ASSERT(num_segments > 0);
  58. ImVec2 p_last = cb.P0;
  59. ImVec2 p_closest;
  60. float p_closest_dist = FLT_MAX;
  61. float t_step = 1.0f / (float)num_segments;
  62. for (int i = 1; i <= num_segments; ++i)
  63. {
  64. ImVec2 p_current = EvalCubicBezier(t_step * i, cb.P0, cb.P1, cb.P2, cb.P3);
  65. ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p);
  66. float dist = ImLengthSqr(p - p_line);
  67. if (dist < p_closest_dist)
  68. {
  69. p_closest = p_line;
  70. p_closest_dist = dist;
  71. }
  72. p_last = p_current;
  73. }
  74. return p_closest;
  75. }
  76. inline float GetDistanceToCubicBezier(
  77. const ImVec2& pos,
  78. const CubicBezier& cubic_bezier,
  79. const int num_segments)
  80. {
  81. const ImVec2 point_on_curve = GetClosestPointOnCubicBezier(num_segments, pos, cubic_bezier);
  82. const ImVec2 to_curve = point_on_curve - pos;
  83. return ImSqrt(ImLengthSqr(to_curve));
  84. }
  85. inline ImRect GetContainingRectForCubicBezier(const CubicBezier& cb)
  86. {
  87. const ImVec2 min = ImVec2(ImMin(cb.P0.x, cb.P3.x), ImMin(cb.P0.y, cb.P3.y));
  88. const ImVec2 max = ImVec2(ImMax(cb.P0.x, cb.P3.x), ImMax(cb.P0.y, cb.P3.y));
  89. const float hover_distance = GImNodes->Style.LinkHoverDistance;
  90. ImRect rect(min, max);
  91. rect.Add(cb.P1);
  92. rect.Add(cb.P2);
  93. rect.Expand(ImVec2(hover_distance, hover_distance));
  94. return rect;
  95. }
  96. inline CubicBezier GetCubicBezier(
  97. ImVec2 start,
  98. ImVec2 end,
  99. const ImNodesAttributeType start_type,
  100. const float line_segments_per_length)
  101. {
  102. assert(
  103. (start_type == ImNodesAttributeType_Input) || (start_type == ImNodesAttributeType_Output));
  104. if (start_type == ImNodesAttributeType_Input)
  105. {
  106. ImSwap(start, end);
  107. }
  108. const float link_length = ImSqrt(ImLengthSqr(end - start));
  109. const ImVec2 offset = ImVec2(0.25f * link_length, 0.f);
  110. CubicBezier cubic_bezier;
  111. cubic_bezier.P0 = start;
  112. cubic_bezier.P1 = start + offset;
  113. cubic_bezier.P2 = end - offset;
  114. cubic_bezier.P3 = end;
  115. cubic_bezier.NumSegments = ImMax(static_cast<int>(link_length * line_segments_per_length), 1);
  116. return cubic_bezier;
  117. }
  118. inline float EvalImplicitLineEq(const ImVec2& p1, const ImVec2& p2, const ImVec2& p)
  119. {
  120. return (p2.y - p1.y) * p.x + (p1.x - p2.x) * p.y + (p2.x * p1.y - p1.x * p2.y);
  121. }
  122. inline int Sign(float val) { return int(val > 0.0f) - int(val < 0.0f); }
  123. inline bool RectangleOverlapsLineSegment(const ImRect& rect, const ImVec2& p1, const ImVec2& p2)
  124. {
  125. // Trivial case: rectangle contains an endpoint
  126. if (rect.Contains(p1) || rect.Contains(p2))
  127. {
  128. return true;
  129. }
  130. // Flip rectangle if necessary
  131. ImRect flip_rect = rect;
  132. if (flip_rect.Min.x > flip_rect.Max.x)
  133. {
  134. ImSwap(flip_rect.Min.x, flip_rect.Max.x);
  135. }
  136. if (flip_rect.Min.y > flip_rect.Max.y)
  137. {
  138. ImSwap(flip_rect.Min.y, flip_rect.Max.y);
  139. }
  140. // Trivial case: line segment lies to one particular side of rectangle
  141. if ((p1.x < flip_rect.Min.x && p2.x < flip_rect.Min.x) ||
  142. (p1.x > flip_rect.Max.x && p2.x > flip_rect.Max.x) ||
  143. (p1.y < flip_rect.Min.y && p2.y < flip_rect.Min.y) ||
  144. (p1.y > flip_rect.Max.y && p2.y > flip_rect.Max.y))
  145. {
  146. return false;
  147. }
  148. const int corner_signs[4] = {
  149. Sign(EvalImplicitLineEq(p1, p2, flip_rect.Min)),
  150. Sign(EvalImplicitLineEq(p1, p2, ImVec2(flip_rect.Max.x, flip_rect.Min.y))),
  151. Sign(EvalImplicitLineEq(p1, p2, ImVec2(flip_rect.Min.x, flip_rect.Max.y))),
  152. Sign(EvalImplicitLineEq(p1, p2, flip_rect.Max))};
  153. int sum = 0;
  154. int sum_abs = 0;
  155. for (int i = 0; i < 4; ++i)
  156. {
  157. sum += corner_signs[i];
  158. sum_abs += abs(corner_signs[i]);
  159. }
  160. // At least one corner of rectangle lies on a different side of line segment
  161. return abs(sum) != sum_abs;
  162. }
  163. inline bool RectangleOverlapsBezier(const ImRect& rectangle, const CubicBezier& cubic_bezier)
  164. {
  165. ImVec2 current =
  166. EvalCubicBezier(0.f, cubic_bezier.P0, cubic_bezier.P1, cubic_bezier.P2, cubic_bezier.P3);
  167. const float dt = 1.0f / cubic_bezier.NumSegments;
  168. for (int s = 0; s < cubic_bezier.NumSegments; ++s)
  169. {
  170. ImVec2 next = EvalCubicBezier(
  171. static_cast<float>((s + 1) * dt),
  172. cubic_bezier.P0,
  173. cubic_bezier.P1,
  174. cubic_bezier.P2,
  175. cubic_bezier.P3);
  176. if (RectangleOverlapsLineSegment(rectangle, current, next))
  177. {
  178. return true;
  179. }
  180. current = next;
  181. }
  182. return false;
  183. }
  184. inline bool RectangleOverlapsLink(
  185. const ImRect& rectangle,
  186. const ImVec2& start,
  187. const ImVec2& end,
  188. const ImNodesAttributeType start_type)
  189. {
  190. // First level: simple rejection test via rectangle overlap:
  191. ImRect lrect = ImRect(start, end);
  192. if (lrect.Min.x > lrect.Max.x)
  193. {
  194. ImSwap(lrect.Min.x, lrect.Max.x);
  195. }
  196. if (lrect.Min.y > lrect.Max.y)
  197. {
  198. ImSwap(lrect.Min.y, lrect.Max.y);
  199. }
  200. if (rectangle.Overlaps(lrect))
  201. {
  202. // First, check if either one or both endpoinds are trivially contained
  203. // in the rectangle
  204. if (rectangle.Contains(start) || rectangle.Contains(end))
  205. {
  206. return true;
  207. }
  208. // Second level of refinement: do a more expensive test against the
  209. // link
  210. const CubicBezier cubic_bezier =
  211. GetCubicBezier(start, end, start_type, GImNodes->Style.LinkLineSegmentsPerLength);
  212. return RectangleOverlapsBezier(rectangle, cubic_bezier);
  213. }
  214. return false;
  215. }
  216. // [SECTION] draw list helper
  217. void ImDrawListGrowChannels(ImDrawList* draw_list, const int num_channels)
  218. {
  219. ImDrawListSplitter& splitter = draw_list->_Splitter;
  220. if (splitter._Count == 1)
  221. {
  222. splitter.Split(draw_list, num_channels + 1);
  223. return;
  224. }
  225. // NOTE: this logic has been lifted from ImDrawListSplitter::Split with slight modifications
  226. // to allow nested splits. The main modification is that we only create new ImDrawChannel
  227. // instances after splitter._Count, instead of over the whole splitter._Channels array like
  228. // the regular ImDrawListSplitter::Split method does.
  229. const int old_channel_capacity = splitter._Channels.Size;
  230. // NOTE: _Channels is not resized down, and therefore _Count <= _Channels.size()!
  231. const int old_channel_count = splitter._Count;
  232. const int requested_channel_count = old_channel_count + num_channels;
  233. if (old_channel_capacity < old_channel_count + num_channels)
  234. {
  235. splitter._Channels.resize(requested_channel_count);
  236. }
  237. splitter._Count = requested_channel_count;
  238. for (int i = old_channel_count; i < requested_channel_count; ++i)
  239. {
  240. ImDrawChannel& channel = splitter._Channels[i];
  241. // If we're inside the old capacity region of the array, we need to reuse the existing
  242. // memory of the command and index buffers.
  243. if (i < old_channel_capacity)
  244. {
  245. channel._CmdBuffer.resize(0);
  246. channel._IdxBuffer.resize(0);
  247. }
  248. // Else, we need to construct new draw channels.
  249. else
  250. {
  251. IM_PLACEMENT_NEW(&channel) ImDrawChannel();
  252. }
  253. {
  254. ImDrawCmd draw_cmd;
  255. draw_cmd.ClipRect = draw_list->_ClipRectStack.back();
  256. draw_cmd.TextureId = draw_list->_TextureIdStack.back();
  257. channel._CmdBuffer.push_back(draw_cmd);
  258. }
  259. }
  260. }
  261. void ImDrawListSplitterSwapChannels(
  262. ImDrawListSplitter& splitter,
  263. const int lhs_idx,
  264. const int rhs_idx)
  265. {
  266. if (lhs_idx == rhs_idx)
  267. {
  268. return;
  269. }
  270. assert(lhs_idx >= 0 && lhs_idx < splitter._Count);
  271. assert(rhs_idx >= 0 && rhs_idx < splitter._Count);
  272. ImDrawChannel& lhs_channel = splitter._Channels[lhs_idx];
  273. ImDrawChannel& rhs_channel = splitter._Channels[rhs_idx];
  274. lhs_channel._CmdBuffer.swap(rhs_channel._CmdBuffer);
  275. lhs_channel._IdxBuffer.swap(rhs_channel._IdxBuffer);
  276. const int current_channel = splitter._Current;
  277. if (current_channel == lhs_idx)
  278. {
  279. splitter._Current = rhs_idx;
  280. }
  281. else if (current_channel == rhs_idx)
  282. {
  283. splitter._Current = lhs_idx;
  284. }
  285. }
  286. void DrawListSet(ImDrawList* window_draw_list)
  287. {
  288. GImNodes->CanvasDrawList = window_draw_list;
  289. GImNodes->NodeIdxToSubmissionIdx.Clear();
  290. GImNodes->NodeIdxSubmissionOrder.clear();
  291. }
  292. // The draw list channels are structured as follows. First we have our base channel, the canvas grid
  293. // on which we render the grid lines in BeginNodeEditor(). The base channel is the reason
  294. // draw_list_submission_idx_to_background_channel_idx offsets the index by one. Each BeginNode()
  295. // call appends two new draw channels, for the node background and foreground. The node foreground
  296. // is the channel into which the node's ImGui content is rendered. Finally, in EndNodeEditor() we
  297. // append one last draw channel for rendering the selection box and the incomplete link on top of
  298. // everything else.
  299. //
  300. // +----------+----------+----------+----------+----------+----------+
  301. // | | | | | | |
  302. // |canvas |node |node |... |... |click |
  303. // |grid |background|foreground| | |interaction
  304. // | | | | | | |
  305. // +----------+----------+----------+----------+----------+----------+
  306. // | |
  307. // | submission idx |
  308. // | |
  309. // -----------------------
  310. void DrawListAddNode(const int node_idx)
  311. {
  312. GImNodes->NodeIdxToSubmissionIdx.SetInt(
  313. static_cast<ImGuiID>(node_idx), GImNodes->NodeIdxSubmissionOrder.Size);
  314. GImNodes->NodeIdxSubmissionOrder.push_back(node_idx);
  315. ImDrawListGrowChannels(GImNodes->CanvasDrawList, 2);
  316. }
  317. void DrawListAppendClickInteractionChannel()
  318. {
  319. // NOTE: don't use this function outside of EndNodeEditor. Using this before all nodes have been
  320. // added will screw up the node draw order.
  321. ImDrawListGrowChannels(GImNodes->CanvasDrawList, 1);
  322. }
  323. int DrawListSubmissionIdxToBackgroundChannelIdx(const int submission_idx)
  324. {
  325. // NOTE: the first channel is the canvas background, i.e. the grid
  326. return 1 + 2 * submission_idx;
  327. }
  328. int DrawListSubmissionIdxToForegroundChannelIdx(const int submission_idx)
  329. {
  330. return DrawListSubmissionIdxToBackgroundChannelIdx(submission_idx) + 1;
  331. }
  332. void DrawListActivateClickInteractionChannel()
  333. {
  334. GImNodes->CanvasDrawList->_Splitter.SetCurrentChannel(
  335. GImNodes->CanvasDrawList, GImNodes->CanvasDrawList->_Splitter._Count - 1);
  336. }
  337. void DrawListActivateCurrentNodeForeground()
  338. {
  339. const int foreground_channel_idx =
  340. DrawListSubmissionIdxToForegroundChannelIdx(GImNodes->NodeIdxSubmissionOrder.Size - 1);
  341. GImNodes->CanvasDrawList->_Splitter.SetCurrentChannel(
  342. GImNodes->CanvasDrawList, foreground_channel_idx);
  343. }
  344. void DrawListActivateNodeBackground(const int node_idx)
  345. {
  346. const int submission_idx =
  347. GImNodes->NodeIdxToSubmissionIdx.GetInt(static_cast<ImGuiID>(node_idx), -1);
  348. // There is a discrepancy in the submitted node count and the rendered node count! Did you call
  349. // one of the following functions
  350. // * EditorContextMoveToNode
  351. // * SetNodeScreenSpacePos
  352. // * SetNodeGridSpacePos
  353. // * SetNodeDraggable
  354. // after the BeginNode/EndNode function calls?
  355. assert(submission_idx != -1);
  356. const int background_channel_idx = DrawListSubmissionIdxToBackgroundChannelIdx(submission_idx);
  357. GImNodes->CanvasDrawList->_Splitter.SetCurrentChannel(
  358. GImNodes->CanvasDrawList, background_channel_idx);
  359. }
  360. void DrawListSwapSubmissionIndices(const int lhs_idx, const int rhs_idx)
  361. {
  362. assert(lhs_idx != rhs_idx);
  363. const int lhs_foreground_channel_idx = DrawListSubmissionIdxToForegroundChannelIdx(lhs_idx);
  364. const int lhs_background_channel_idx = DrawListSubmissionIdxToBackgroundChannelIdx(lhs_idx);
  365. const int rhs_foreground_channel_idx = DrawListSubmissionIdxToForegroundChannelIdx(rhs_idx);
  366. const int rhs_background_channel_idx = DrawListSubmissionIdxToBackgroundChannelIdx(rhs_idx);
  367. ImDrawListSplitterSwapChannels(
  368. GImNodes->CanvasDrawList->_Splitter,
  369. lhs_background_channel_idx,
  370. rhs_background_channel_idx);
  371. ImDrawListSplitterSwapChannels(
  372. GImNodes->CanvasDrawList->_Splitter,
  373. lhs_foreground_channel_idx,
  374. rhs_foreground_channel_idx);
  375. }
  376. void DrawListSortChannelsByDepth(const ImVector<int>& node_idx_depth_order)
  377. {
  378. if (GImNodes->NodeIdxToSubmissionIdx.Data.Size < 2)
  379. {
  380. return;
  381. }
  382. assert(node_idx_depth_order.Size == GImNodes->NodeIdxSubmissionOrder.Size);
  383. int start_idx = node_idx_depth_order.Size - 1;
  384. while (node_idx_depth_order[start_idx] == GImNodes->NodeIdxSubmissionOrder[start_idx])
  385. {
  386. if (--start_idx == 0)
  387. {
  388. // early out if submission order and depth order are the same
  389. return;
  390. }
  391. }
  392. // TODO: this is an O(N^2) algorithm. It might be worthwhile revisiting this to see if the time
  393. // complexity can be reduced.
  394. for (int depth_idx = start_idx; depth_idx > 0; --depth_idx)
  395. {
  396. const int node_idx = node_idx_depth_order[depth_idx];
  397. // Find the current index of the node_idx in the submission order array
  398. int submission_idx = -1;
  399. for (int i = 0; i < GImNodes->NodeIdxSubmissionOrder.Size; ++i)
  400. {
  401. if (GImNodes->NodeIdxSubmissionOrder[i] == node_idx)
  402. {
  403. submission_idx = i;
  404. break;
  405. }
  406. }
  407. assert(submission_idx >= 0);
  408. if (submission_idx == depth_idx)
  409. {
  410. continue;
  411. }
  412. for (int j = submission_idx; j < depth_idx; ++j)
  413. {
  414. DrawListSwapSubmissionIndices(j, j + 1);
  415. ImSwap(GImNodes->NodeIdxSubmissionOrder[j], GImNodes->NodeIdxSubmissionOrder[j + 1]);
  416. }
  417. }
  418. }
  419. // [SECTION] ui state logic
  420. ImVec2 GetScreenSpacePinCoordinates(
  421. const ImRect& node_rect,
  422. const ImRect& attribute_rect,
  423. const ImNodesAttributeType type)
  424. {
  425. assert(type == ImNodesAttributeType_Input || type == ImNodesAttributeType_Output);
  426. const float x = type == ImNodesAttributeType_Input
  427. ? (node_rect.Min.x - GImNodes->Style.PinOffset)
  428. : (node_rect.Max.x + GImNodes->Style.PinOffset);
  429. return ImVec2(x, 0.5f * (attribute_rect.Min.y + attribute_rect.Max.y));
  430. }
  431. ImVec2 GetScreenSpacePinCoordinates(const ImNodesEditorContext& editor, const ImPinData& pin)
  432. {
  433. const ImRect& parent_node_rect = editor.Nodes.Pool[pin.ParentNodeIdx].Rect;
  434. return GetScreenSpacePinCoordinates(parent_node_rect, pin.AttributeRect, pin.Type);
  435. }
  436. bool MouseInCanvas()
  437. {
  438. // This flag should be true either when hovering or clicking something in the canvas.
  439. const bool is_window_hovered_or_focused = ImGui::IsWindowHovered() || ImGui::IsWindowFocused();
  440. return is_window_hovered_or_focused &&
  441. GImNodes->CanvasRectScreenSpace.Contains(ImGui::GetMousePos());
  442. }
  443. void BeginNodeSelection(ImNodesEditorContext& editor, const int node_idx)
  444. {
  445. // Don't start selecting a node if we are e.g. already creating and dragging
  446. // a new link! New link creation can happen when the mouse is clicked over
  447. // a node, but within the hover radius of a pin.
  448. if (editor.ClickInteraction.Type != ImNodesClickInteractionType_None)
  449. {
  450. return;
  451. }
  452. editor.ClickInteraction.Type = ImNodesClickInteractionType_Node;
  453. // If the node is not already contained in the selection, then we want only
  454. // the interaction node to be selected, effective immediately.
  455. //
  456. // Otherwise, we want to allow for the possibility of multiple nodes to be
  457. // moved at once.
  458. if (!editor.SelectedNodeIndices.contains(node_idx))
  459. {
  460. editor.SelectedNodeIndices.clear();
  461. editor.SelectedLinkIndices.clear();
  462. editor.SelectedNodeIndices.push_back(node_idx);
  463. // Ensure that individually selected nodes get rendered on top
  464. ImVector<int>& depth_stack = editor.NodeDepthOrder;
  465. const int* const elem = depth_stack.find(node_idx);
  466. assert(elem != depth_stack.end());
  467. depth_stack.erase(elem);
  468. depth_stack.push_back(node_idx);
  469. }
  470. }
  471. void BeginLinkSelection(ImNodesEditorContext& editor, const int link_idx)
  472. {
  473. editor.ClickInteraction.Type = ImNodesClickInteractionType_Link;
  474. // When a link is selected, clear all other selections, and insert the link
  475. // as the sole selection.
  476. editor.SelectedNodeIndices.clear();
  477. editor.SelectedLinkIndices.clear();
  478. editor.SelectedLinkIndices.push_back(link_idx);
  479. }
  480. void BeginLinkDetach(ImNodesEditorContext& editor, const int link_idx, const int detach_pin_idx)
  481. {
  482. const ImLinkData& link = editor.Links.Pool[link_idx];
  483. ImClickInteractionState& state = editor.ClickInteraction;
  484. state.Type = ImNodesClickInteractionType_LinkCreation;
  485. state.LinkCreation.EndPinIdx.Reset();
  486. state.LinkCreation.StartPinIdx =
  487. detach_pin_idx == link.StartPinIdx ? link.EndPinIdx : link.StartPinIdx;
  488. GImNodes->DeletedLinkIdx = link_idx;
  489. }
  490. void BeginLinkInteraction(ImNodesEditorContext& editor, const int link_idx)
  491. {
  492. // Check the 'click and drag to detach' case.
  493. if (GImNodes->HoveredPinIdx.HasValue() &&
  494. (editor.Pins.Pool[GImNodes->HoveredPinIdx.Value()].Flags &
  495. ImNodesAttributeFlags_EnableLinkDetachWithDragClick) != 0)
  496. {
  497. BeginLinkDetach(editor, link_idx, GImNodes->HoveredPinIdx.Value());
  498. editor.ClickInteraction.LinkCreation.Type = ImNodesLinkCreationType_FromDetach;
  499. }
  500. // If we aren't near a pin, check if we are clicking the link with the
  501. // modifier pressed. This may also result in a link detach via clicking.
  502. else
  503. {
  504. const bool modifier_pressed = GImNodes->Io.LinkDetachWithModifierClick.Modifier == NULL
  505. ? false
  506. : *GImNodes->Io.LinkDetachWithModifierClick.Modifier;
  507. if (modifier_pressed)
  508. {
  509. const ImLinkData& link = editor.Links.Pool[link_idx];
  510. const ImPinData& start_pin = editor.Pins.Pool[link.StartPinIdx];
  511. const ImPinData& end_pin = editor.Pins.Pool[link.EndPinIdx];
  512. const ImVec2& mouse_pos = GImNodes->MousePos;
  513. const float dist_to_start = ImLengthSqr(start_pin.Pos - mouse_pos);
  514. const float dist_to_end = ImLengthSqr(end_pin.Pos - mouse_pos);
  515. const int closest_pin_idx =
  516. dist_to_start < dist_to_end ? link.StartPinIdx : link.EndPinIdx;
  517. editor.ClickInteraction.Type = ImNodesClickInteractionType_LinkCreation;
  518. BeginLinkDetach(editor, link_idx, closest_pin_idx);
  519. editor.ClickInteraction.LinkCreation.Type = ImNodesLinkCreationType_FromDetach;
  520. }
  521. else
  522. {
  523. BeginLinkSelection(editor, link_idx);
  524. }
  525. }
  526. }
  527. void BeginLinkCreation(ImNodesEditorContext& editor, const int hovered_pin_idx)
  528. {
  529. editor.ClickInteraction.Type = ImNodesClickInteractionType_LinkCreation;
  530. editor.ClickInteraction.LinkCreation.StartPinIdx = hovered_pin_idx;
  531. editor.ClickInteraction.LinkCreation.EndPinIdx.Reset();
  532. editor.ClickInteraction.LinkCreation.Type = ImNodesLinkCreationType_Standard;
  533. GImNodes->ImNodesUIState |= ImNodesUIState_LinkStarted;
  534. }
  535. static inline bool IsMiniMapHovered();
  536. void BeginCanvasInteraction(ImNodesEditorContext& editor)
  537. {
  538. const bool any_ui_element_hovered =
  539. GImNodes->HoveredNodeIdx.HasValue() || GImNodes->HoveredLinkIdx.HasValue() ||
  540. GImNodes->HoveredPinIdx.HasValue() || ImGui::IsAnyItemHovered();
  541. const bool mouse_not_in_canvas = !MouseInCanvas();
  542. if (editor.ClickInteraction.Type != ImNodesClickInteractionType_None ||
  543. any_ui_element_hovered || mouse_not_in_canvas)
  544. {
  545. return;
  546. }
  547. const bool started_panning = GImNodes->AltMouseClicked;
  548. // Handle mini-map interactions
  549. if (IsMiniMapHovered())
  550. {
  551. if (started_panning)
  552. {
  553. editor.ClickInteraction.Type = ImNodesClickInteractionType_MiniMapPanning;
  554. }
  555. else if (GImNodes->LeftMouseReleased)
  556. {
  557. editor.ClickInteraction.Type = ImNodesClickInteractionType_MiniMapSnapping;
  558. }
  559. else if (GImNodes->AltMouseScrollDelta != 0.f)
  560. {
  561. editor.ClickInteraction.Type = ImNodesClickInteractionType_MiniMapZooming;
  562. }
  563. }
  564. // Handle normal editor interactions
  565. else
  566. {
  567. if (started_panning)
  568. {
  569. editor.ClickInteraction.Type = ImNodesClickInteractionType_Panning;
  570. }
  571. else if (GImNodes->LeftMouseClicked)
  572. {
  573. editor.ClickInteraction.Type = ImNodesClickInteractionType_BoxSelection;
  574. editor.ClickInteraction.BoxSelector.Rect.Min = GImNodes->MousePos;
  575. }
  576. }
  577. }
  578. void BoxSelectorUpdateSelection(ImNodesEditorContext& editor, ImRect box_rect)
  579. {
  580. // Invert box selector coordinates as needed
  581. if (box_rect.Min.x > box_rect.Max.x)
  582. {
  583. ImSwap(box_rect.Min.x, box_rect.Max.x);
  584. }
  585. if (box_rect.Min.y > box_rect.Max.y)
  586. {
  587. ImSwap(box_rect.Min.y, box_rect.Max.y);
  588. }
  589. // Update node selection
  590. editor.SelectedNodeIndices.clear();
  591. // Test for overlap against node rectangles
  592. for (int node_idx = 0; node_idx < editor.Nodes.Pool.size(); ++node_idx)
  593. {
  594. if (editor.Nodes.InUse[node_idx])
  595. {
  596. ImNodeData& node = editor.Nodes.Pool[node_idx];
  597. if (box_rect.Overlaps(node.Rect))
  598. {
  599. editor.SelectedNodeIndices.push_back(node_idx);
  600. }
  601. }
  602. }
  603. // Update link selection
  604. editor.SelectedLinkIndices.clear();
  605. // Test for overlap against links
  606. for (int link_idx = 0; link_idx < editor.Links.Pool.size(); ++link_idx)
  607. {
  608. if (editor.Links.InUse[link_idx])
  609. {
  610. const ImLinkData& link = editor.Links.Pool[link_idx];
  611. const ImPinData& pin_start = editor.Pins.Pool[link.StartPinIdx];
  612. const ImPinData& pin_end = editor.Pins.Pool[link.EndPinIdx];
  613. const ImRect& node_start_rect = editor.Nodes.Pool[pin_start.ParentNodeIdx].Rect;
  614. const ImRect& node_end_rect = editor.Nodes.Pool[pin_end.ParentNodeIdx].Rect;
  615. const ImVec2 start = GetScreenSpacePinCoordinates(
  616. node_start_rect, pin_start.AttributeRect, pin_start.Type);
  617. const ImVec2 end =
  618. GetScreenSpacePinCoordinates(node_end_rect, pin_end.AttributeRect, pin_end.Type);
  619. // Test
  620. if (RectangleOverlapsLink(box_rect, start, end, pin_start.Type))
  621. {
  622. editor.SelectedLinkIndices.push_back(link_idx);
  623. }
  624. }
  625. }
  626. }
  627. void TranslateSelectedNodes(ImNodesEditorContext& editor)
  628. {
  629. if (GImNodes->LeftMouseDragging)
  630. {
  631. const ImGuiIO& io = ImGui::GetIO();
  632. for (int i = 0; i < editor.SelectedNodeIndices.size(); ++i)
  633. {
  634. const int node_idx = editor.SelectedNodeIndices[i];
  635. ImNodeData& node = editor.Nodes.Pool[node_idx];
  636. if (node.Draggable)
  637. {
  638. node.Origin += io.MouseDelta;
  639. }
  640. }
  641. }
  642. }
  643. struct LinkPredicate
  644. {
  645. bool operator()(const ImLinkData& lhs, const ImLinkData& rhs) const
  646. {
  647. // Do a unique compare by sorting the pins' addresses.
  648. // This catches duplicate links, whether they are in the
  649. // same direction or not.
  650. // Sorting by pin index should have the uniqueness guarantees as sorting
  651. // by id -- each unique id will get one slot in the link pool array.
  652. int lhs_start = lhs.StartPinIdx;
  653. int lhs_end = lhs.EndPinIdx;
  654. int rhs_start = rhs.StartPinIdx;
  655. int rhs_end = rhs.EndPinIdx;
  656. if (lhs_start > lhs_end)
  657. {
  658. ImSwap(lhs_start, lhs_end);
  659. }
  660. if (rhs_start > rhs_end)
  661. {
  662. ImSwap(rhs_start, rhs_end);
  663. }
  664. return lhs_start == rhs_start && lhs_end == rhs_end;
  665. }
  666. };
  667. ImOptionalIndex FindDuplicateLink(
  668. const ImNodesEditorContext& editor,
  669. const int start_pin_idx,
  670. const int end_pin_idx)
  671. {
  672. ImLinkData test_link(0);
  673. test_link.StartPinIdx = start_pin_idx;
  674. test_link.EndPinIdx = end_pin_idx;
  675. for (int link_idx = 0; link_idx < editor.Links.Pool.size(); ++link_idx)
  676. {
  677. const ImLinkData& link = editor.Links.Pool[link_idx];
  678. if (LinkPredicate()(test_link, link) && editor.Links.InUse[link_idx])
  679. {
  680. return ImOptionalIndex(link_idx);
  681. }
  682. }
  683. return ImOptionalIndex();
  684. }
  685. bool ShouldLinkSnapToPin(
  686. const ImNodesEditorContext& editor,
  687. const ImPinData& start_pin,
  688. const int hovered_pin_idx,
  689. const ImOptionalIndex duplicate_link)
  690. {
  691. const ImPinData& end_pin = editor.Pins.Pool[hovered_pin_idx];
  692. // The end pin must be in a different node
  693. if (start_pin.ParentNodeIdx == end_pin.ParentNodeIdx)
  694. {
  695. return false;
  696. }
  697. // The end pin must be of a different type
  698. if (start_pin.Type == end_pin.Type)
  699. {
  700. return false;
  701. }
  702. // The link to be created must not be a duplicate, unless it is the link which was created on
  703. // snap. In that case we want to snap, since we want it to appear visually as if the created
  704. // link remains snapped to the pin.
  705. if (duplicate_link.HasValue() && !(duplicate_link == GImNodes->SnapLinkIdx))
  706. {
  707. return false;
  708. }
  709. return true;
  710. }
  711. void ClickInteractionUpdate(ImNodesEditorContext& editor)
  712. {
  713. switch (editor.ClickInteraction.Type)
  714. {
  715. case ImNodesClickInteractionType_BoxSelection:
  716. {
  717. ImRect& box_rect = editor.ClickInteraction.BoxSelector.Rect;
  718. box_rect.Max = GImNodes->MousePos;
  719. BoxSelectorUpdateSelection(editor, box_rect);
  720. const ImU32 box_selector_color = GImNodes->Style.Colors[ImNodesCol_BoxSelector];
  721. const ImU32 box_selector_outline = GImNodes->Style.Colors[ImNodesCol_BoxSelectorOutline];
  722. GImNodes->CanvasDrawList->AddRectFilled(box_rect.Min, box_rect.Max, box_selector_color);
  723. GImNodes->CanvasDrawList->AddRect(box_rect.Min, box_rect.Max, box_selector_outline);
  724. if (GImNodes->LeftMouseReleased)
  725. {
  726. ImVector<int>& depth_stack = editor.NodeDepthOrder;
  727. const ImVector<int>& selected_idxs = editor.SelectedNodeIndices;
  728. // Bump the selected node indices, in order, to the top of the depth stack.
  729. // NOTE: this algorithm has worst case time complexity of O(N^2), if the node selection
  730. // is ~ N (due to selected_idxs.contains()).
  731. if ((selected_idxs.Size > 0) && (selected_idxs.Size < depth_stack.Size))
  732. {
  733. int num_moved = 0; // The number of indices moved. Stop after selected_idxs.Size
  734. for (int i = 0; i < depth_stack.Size - selected_idxs.Size; ++i)
  735. {
  736. for (int node_idx = depth_stack[i]; selected_idxs.contains(node_idx);
  737. node_idx = depth_stack[i])
  738. {
  739. depth_stack.erase(depth_stack.begin() + static_cast<size_t>(i));
  740. depth_stack.push_back(node_idx);
  741. ++num_moved;
  742. }
  743. if (num_moved == selected_idxs.Size)
  744. {
  745. break;
  746. }
  747. }
  748. }
  749. editor.ClickInteraction.Type = ImNodesClickInteractionType_None;
  750. }
  751. }
  752. break;
  753. case ImNodesClickInteractionType_Node:
  754. {
  755. TranslateSelectedNodes(editor);
  756. if (GImNodes->LeftMouseReleased)
  757. {
  758. editor.ClickInteraction.Type = ImNodesClickInteractionType_None;
  759. }
  760. }
  761. break;
  762. case ImNodesClickInteractionType_Link:
  763. {
  764. if (GImNodes->LeftMouseReleased)
  765. {
  766. editor.ClickInteraction.Type = ImNodesClickInteractionType_None;
  767. }
  768. }
  769. break;
  770. case ImNodesClickInteractionType_LinkCreation:
  771. {
  772. const ImPinData& start_pin =
  773. editor.Pins.Pool[editor.ClickInteraction.LinkCreation.StartPinIdx];
  774. const ImOptionalIndex maybe_duplicate_link_idx =
  775. GImNodes->HoveredPinIdx.HasValue()
  776. ? FindDuplicateLink(
  777. editor,
  778. editor.ClickInteraction.LinkCreation.StartPinIdx,
  779. GImNodes->HoveredPinIdx.Value())
  780. : ImOptionalIndex();
  781. const bool should_snap =
  782. GImNodes->HoveredPinIdx.HasValue() &&
  783. ShouldLinkSnapToPin(
  784. editor, start_pin, GImNodes->HoveredPinIdx.Value(), maybe_duplicate_link_idx);
  785. // If we created on snap and the hovered pin is empty or changed, then we need signal that
  786. // the link's state has changed.
  787. const bool snapping_pin_changed =
  788. editor.ClickInteraction.LinkCreation.EndPinIdx.HasValue() &&
  789. !(GImNodes->HoveredPinIdx == editor.ClickInteraction.LinkCreation.EndPinIdx);
  790. // Detach the link that was created by this link event if it's no longer in snap range
  791. if (snapping_pin_changed && GImNodes->SnapLinkIdx.HasValue())
  792. {
  793. BeginLinkDetach(
  794. editor,
  795. GImNodes->SnapLinkIdx.Value(),
  796. editor.ClickInteraction.LinkCreation.EndPinIdx.Value());
  797. }
  798. const ImVec2 start_pos = GetScreenSpacePinCoordinates(editor, start_pin);
  799. // If we are within the hover radius of a receiving pin, snap the link
  800. // endpoint to it
  801. const ImVec2 end_pos = should_snap
  802. ? GetScreenSpacePinCoordinates(
  803. editor, editor.Pins.Pool[GImNodes->HoveredPinIdx.Value()])
  804. : GImNodes->MousePos;
  805. const CubicBezier cubic_bezier = GetCubicBezier(
  806. start_pos, end_pos, start_pin.Type, GImNodes->Style.LinkLineSegmentsPerLength);
  807. #if IMGUI_VERSION_NUM < 18000
  808. GImNodes->CanvasDrawList->AddBezierCurve(
  809. #else
  810. GImNodes->CanvasDrawList->AddBezierCubic(
  811. #endif
  812. cubic_bezier.P0,
  813. cubic_bezier.P1,
  814. cubic_bezier.P2,
  815. cubic_bezier.P3,
  816. GImNodes->Style.Colors[ImNodesCol_Link],
  817. GImNodes->Style.LinkThickness,
  818. cubic_bezier.NumSegments);
  819. const bool link_creation_on_snap =
  820. GImNodes->HoveredPinIdx.HasValue() &&
  821. (editor.Pins.Pool[GImNodes->HoveredPinIdx.Value()].Flags &
  822. ImNodesAttributeFlags_EnableLinkCreationOnSnap);
  823. if (!should_snap)
  824. {
  825. editor.ClickInteraction.LinkCreation.EndPinIdx.Reset();
  826. }
  827. const bool create_link =
  828. should_snap && (GImNodes->LeftMouseReleased || link_creation_on_snap);
  829. if (create_link && !maybe_duplicate_link_idx.HasValue())
  830. {
  831. // Avoid send OnLinkCreated() events every frame if the snap link is not saved
  832. // (only applies for EnableLinkCreationOnSnap)
  833. if (!GImNodes->LeftMouseReleased &&
  834. editor.ClickInteraction.LinkCreation.EndPinIdx == GImNodes->HoveredPinIdx)
  835. {
  836. break;
  837. }
  838. GImNodes->ImNodesUIState |= ImNodesUIState_LinkCreated;
  839. editor.ClickInteraction.LinkCreation.EndPinIdx = GImNodes->HoveredPinIdx.Value();
  840. }
  841. if (GImNodes->LeftMouseReleased)
  842. {
  843. editor.ClickInteraction.Type = ImNodesClickInteractionType_None;
  844. if (!create_link)
  845. {
  846. GImNodes->ImNodesUIState |= ImNodesUIState_LinkDropped;
  847. }
  848. }
  849. }
  850. break;
  851. case ImNodesClickInteractionType_Panning:
  852. {
  853. const bool dragging = GImNodes->AltMouseDragging;
  854. if (dragging)
  855. {
  856. editor.Panning += ImGui::GetIO().MouseDelta;
  857. }
  858. else
  859. {
  860. editor.ClickInteraction.Type = ImNodesClickInteractionType_None;
  861. }
  862. }
  863. break;
  864. case ImNodesClickInteractionType_MiniMapPanning:
  865. {
  866. const bool dragging = GImNodes->AltMouseDragging;
  867. if (dragging)
  868. {
  869. editor.Panning += ImGui::GetIO().MouseDelta / GImNodes->MiniMapZoom;
  870. }
  871. else
  872. {
  873. editor.ClickInteraction.Type = ImNodesClickInteractionType_None;
  874. }
  875. }
  876. break;
  877. case ImNodesClickInteractionType_MiniMapZooming:
  878. {
  879. GImNodes->MiniMapZoom = fmaxf(
  880. 0.05f,
  881. fminf(
  882. GImNodes->MiniMapZoom +
  883. 0.1f * GImNodes->MiniMapZoom * GImNodes->AltMouseScrollDelta,
  884. 1.f));
  885. editor.ClickInteraction.Type = ImNodesClickInteractionType_None;
  886. }
  887. break;
  888. case ImNodesClickInteractionType_MiniMapSnapping:
  889. {
  890. editor.Panning += GImNodes->MiniMapRectSnappingOffset;
  891. GImNodes->MiniMapRectSnappingOffset = ImVec2(0.f, 0.f);
  892. editor.ClickInteraction.Type = ImNodesClickInteractionType_None;
  893. }
  894. break;
  895. case ImNodesClickInteractionType_ImGuiItem:
  896. {
  897. if (GImNodes->LeftMouseReleased)
  898. {
  899. editor.ClickInteraction.Type = ImNodesClickInteractionType_None;
  900. }
  901. }
  902. case ImNodesClickInteractionType_None:
  903. break;
  904. default:
  905. assert(!"Unreachable code!");
  906. break;
  907. }
  908. }
  909. void ResolveOccludedPins(const ImNodesEditorContext& editor, ImVector<int>& occluded_pin_indices)
  910. {
  911. const ImVector<int>& depth_stack = editor.NodeDepthOrder;
  912. occluded_pin_indices.resize(0);
  913. if (depth_stack.Size < 2)
  914. {
  915. return;
  916. }
  917. // For each node in the depth stack
  918. for (int depth_idx = 0; depth_idx < (depth_stack.Size - 1); ++depth_idx)
  919. {
  920. const ImNodeData& node_below = editor.Nodes.Pool[depth_stack[depth_idx]];
  921. // Iterate over the rest of the depth stack to find nodes overlapping the pins
  922. for (int next_depth_idx = depth_idx + 1; next_depth_idx < depth_stack.Size;
  923. ++next_depth_idx)
  924. {
  925. const ImRect& rect_above = editor.Nodes.Pool[depth_stack[next_depth_idx]].Rect;
  926. // Iterate over each pin
  927. for (int idx = 0; idx < node_below.PinIndices.Size; ++idx)
  928. {
  929. const int pin_idx = node_below.PinIndices[idx];
  930. const ImVec2& pin_pos = editor.Pins.Pool[pin_idx].Pos;
  931. if (rect_above.Contains(pin_pos))
  932. {
  933. occluded_pin_indices.push_back(pin_idx);
  934. }
  935. }
  936. }
  937. }
  938. }
  939. ImOptionalIndex ResolveHoveredPin(
  940. const ImObjectPool<ImPinData>& pins,
  941. const ImVector<int>& occluded_pin_indices)
  942. {
  943. float smallest_distance = FLT_MAX;
  944. ImOptionalIndex pin_idx_with_smallest_distance;
  945. const float hover_radius_sqr = GImNodes->Style.PinHoverRadius * GImNodes->Style.PinHoverRadius;
  946. for (int idx = 0; idx < pins.Pool.Size; ++idx)
  947. {
  948. if (!pins.InUse[idx])
  949. {
  950. continue;
  951. }
  952. if (occluded_pin_indices.contains(idx))
  953. {
  954. continue;
  955. }
  956. const ImVec2& pin_pos = pins.Pool[idx].Pos;
  957. const float distance_sqr = ImLengthSqr(pin_pos - GImNodes->MousePos);
  958. // TODO: GImNodes->Style.PinHoverRadius needs to be copied into pin data and the pin-local
  959. // value used here. This is no longer called in BeginAttribute/EndAttribute scope and the
  960. // detected pin might have a different hover radius than what the user had when calling
  961. // BeginAttribute/EndAttribute.
  962. if (distance_sqr < hover_radius_sqr && distance_sqr < smallest_distance)
  963. {
  964. smallest_distance = distance_sqr;
  965. pin_idx_with_smallest_distance = idx;
  966. }
  967. }
  968. return pin_idx_with_smallest_distance;
  969. }
  970. ImOptionalIndex ResolveHoveredNode(const ImVector<int>& depth_stack)
  971. {
  972. if (GImNodes->NodeIndicesOverlappingWithMouse.size() == 0)
  973. {
  974. return ImOptionalIndex();
  975. }
  976. if (GImNodes->NodeIndicesOverlappingWithMouse.size() == 1)
  977. {
  978. return ImOptionalIndex(GImNodes->NodeIndicesOverlappingWithMouse[0]);
  979. }
  980. int largest_depth_idx = -1;
  981. int node_idx_on_top = -1;
  982. for (int i = 0; i < GImNodes->NodeIndicesOverlappingWithMouse.size(); ++i)
  983. {
  984. const int node_idx = GImNodes->NodeIndicesOverlappingWithMouse[i];
  985. for (int depth_idx = 0; depth_idx < depth_stack.size(); ++depth_idx)
  986. {
  987. if (depth_stack[depth_idx] == node_idx && (depth_idx > largest_depth_idx))
  988. {
  989. largest_depth_idx = depth_idx;
  990. node_idx_on_top = node_idx;
  991. }
  992. }
  993. }
  994. assert(node_idx_on_top != -1);
  995. return ImOptionalIndex(node_idx_on_top);
  996. }
  997. ImOptionalIndex ResolveHoveredLink(
  998. const ImObjectPool<ImLinkData>& links,
  999. const ImObjectPool<ImPinData>& pins)
  1000. {
  1001. float smallest_distance = FLT_MAX;
  1002. ImOptionalIndex link_idx_with_smallest_distance;
  1003. // There are two ways a link can be detected as "hovered".
  1004. // 1. The link is within hover distance to the mouse. The closest such link is selected as being
  1005. // hovered over.
  1006. // 2. If the link is connected to the currently hovered pin.
  1007. //
  1008. // The latter is a requirement for link detaching with drag click to work, as both a link and
  1009. // pin are required to be hovered over for the feature to work.
  1010. for (int idx = 0; idx < links.Pool.Size; ++idx)
  1011. {
  1012. if (!links.InUse[idx])
  1013. {
  1014. continue;
  1015. }
  1016. const ImLinkData& link = links.Pool[idx];
  1017. const ImPinData& start_pin = pins.Pool[link.StartPinIdx];
  1018. const ImPinData& end_pin = pins.Pool[link.EndPinIdx];
  1019. if (GImNodes->HoveredPinIdx == link.StartPinIdx ||
  1020. GImNodes->HoveredPinIdx == link.EndPinIdx)
  1021. {
  1022. return idx;
  1023. }
  1024. // TODO: the calculated CubicBeziers could be cached since we generate them again when
  1025. // rendering the links
  1026. const CubicBezier cubic_bezier = GetCubicBezier(
  1027. start_pin.Pos, end_pin.Pos, start_pin.Type, GImNodes->Style.LinkLineSegmentsPerLength);
  1028. // The distance test
  1029. {
  1030. const ImRect link_rect = GetContainingRectForCubicBezier(cubic_bezier);
  1031. // First, do a simple bounding box test against the box containing the link
  1032. // to see whether calculating the distance to the link is worth doing.
  1033. if (link_rect.Contains(GImNodes->MousePos))
  1034. {
  1035. const float distance = GetDistanceToCubicBezier(
  1036. GImNodes->MousePos, cubic_bezier, cubic_bezier.NumSegments);
  1037. // TODO: GImNodes->Style.LinkHoverDistance could be also copied into ImLinkData,
  1038. // since we're not calling this function in the same scope as ImNodes::Link(). The
  1039. // rendered/detected link might have a different hover distance than what the user
  1040. // had specified when calling Link()
  1041. if (distance < GImNodes->Style.LinkHoverDistance && distance < smallest_distance)
  1042. {
  1043. smallest_distance = distance;
  1044. link_idx_with_smallest_distance = idx;
  1045. }
  1046. }
  1047. }
  1048. }
  1049. return link_idx_with_smallest_distance;
  1050. }
  1051. // [SECTION] render helpers
  1052. inline ImVec2 ScreenSpaceToGridSpace(const ImNodesEditorContext& editor, const ImVec2& v)
  1053. {
  1054. return v - GImNodes->CanvasOriginScreenSpace - editor.Panning;
  1055. }
  1056. inline ImVec2 GridSpaceToScreenSpace(const ImNodesEditorContext& editor, const ImVec2& v)
  1057. {
  1058. return v + GImNodes->CanvasOriginScreenSpace + editor.Panning;
  1059. }
  1060. inline ImVec2 GridSpaceToEditorSpace(const ImNodesEditorContext& editor, const ImVec2& v)
  1061. {
  1062. return v + editor.Panning;
  1063. }
  1064. inline ImVec2 EditorSpaceToGridSpace(const ImNodesEditorContext& editor, const ImVec2& v)
  1065. {
  1066. return v - editor.Panning;
  1067. }
  1068. inline ImVec2 EditorSpaceToScreenSpace(const ImVec2& v)
  1069. {
  1070. return GImNodes->CanvasOriginScreenSpace + v;
  1071. }
  1072. inline ImRect GetItemRect() { return ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); }
  1073. inline ImVec2 GetNodeTitleBarOrigin(const ImNodeData& node)
  1074. {
  1075. return node.Origin + node.LayoutStyle.Padding;
  1076. }
  1077. inline ImVec2 GetNodeContentOrigin(const ImNodeData& node)
  1078. {
  1079. const ImVec2 title_bar_height =
  1080. ImVec2(0.f, node.TitleBarContentRect.GetHeight() + 2.0f * node.LayoutStyle.Padding.y);
  1081. return node.Origin + title_bar_height + node.LayoutStyle.Padding;
  1082. }
  1083. inline ImRect GetNodeTitleRect(const ImNodeData& node)
  1084. {
  1085. ImRect expanded_title_rect = node.TitleBarContentRect;
  1086. expanded_title_rect.Expand(node.LayoutStyle.Padding);
  1087. return ImRect(
  1088. expanded_title_rect.Min,
  1089. expanded_title_rect.Min + ImVec2(node.Rect.GetWidth(), 0.f) +
  1090. ImVec2(0.f, expanded_title_rect.GetHeight()));
  1091. }
  1092. void DrawGrid(ImNodesEditorContext& editor, const ImVec2& canvas_size)
  1093. {
  1094. const ImVec2 offset = editor.Panning;
  1095. for (float x = fmodf(offset.x, GImNodes->Style.GridSpacing); x < canvas_size.x;
  1096. x += GImNodes->Style.GridSpacing)
  1097. {
  1098. GImNodes->CanvasDrawList->AddLine(
  1099. EditorSpaceToScreenSpace(ImVec2(x, 0.0f)),
  1100. EditorSpaceToScreenSpace(ImVec2(x, canvas_size.y)),
  1101. GImNodes->Style.Colors[ImNodesCol_GridLine]);
  1102. }
  1103. for (float y = fmodf(offset.y, GImNodes->Style.GridSpacing); y < canvas_size.y;
  1104. y += GImNodes->Style.GridSpacing)
  1105. {
  1106. GImNodes->CanvasDrawList->AddLine(
  1107. EditorSpaceToScreenSpace(ImVec2(0.0f, y)),
  1108. EditorSpaceToScreenSpace(ImVec2(canvas_size.x, y)),
  1109. GImNodes->Style.Colors[ImNodesCol_GridLine]);
  1110. }
  1111. }
  1112. struct QuadOffsets
  1113. {
  1114. ImVec2 TopLeft, BottomLeft, BottomRight, TopRight;
  1115. };
  1116. QuadOffsets CalculateQuadOffsets(const float side_length)
  1117. {
  1118. const float half_side = 0.5f * side_length;
  1119. QuadOffsets offset;
  1120. offset.TopLeft = ImVec2(-half_side, half_side);
  1121. offset.BottomLeft = ImVec2(-half_side, -half_side);
  1122. offset.BottomRight = ImVec2(half_side, -half_side);
  1123. offset.TopRight = ImVec2(half_side, half_side);
  1124. return offset;
  1125. }
  1126. struct TriangleOffsets
  1127. {
  1128. ImVec2 TopLeft, BottomLeft, Right;
  1129. };
  1130. TriangleOffsets CalculateTriangleOffsets(const float side_length)
  1131. {
  1132. // Calculates the Vec2 offsets from an equilateral triangle's midpoint to
  1133. // its vertices. Here is how the left_offset and right_offset are
  1134. // calculated.
  1135. //
  1136. // For an equilateral triangle of side length s, the
  1137. // triangle's height, h, is h = s * sqrt(3) / 2.
  1138. //
  1139. // The length from the base to the midpoint is (1 / 3) * h. The length from
  1140. // the midpoint to the triangle vertex is (2 / 3) * h.
  1141. const float sqrt_3 = sqrtf(3.0f);
  1142. const float left_offset = -0.1666666666667f * sqrt_3 * side_length;
  1143. const float right_offset = 0.333333333333f * sqrt_3 * side_length;
  1144. const float vertical_offset = 0.5f * side_length;
  1145. TriangleOffsets offset;
  1146. offset.TopLeft = ImVec2(left_offset, vertical_offset);
  1147. offset.BottomLeft = ImVec2(left_offset, -vertical_offset);
  1148. offset.Right = ImVec2(right_offset, 0.f);
  1149. return offset;
  1150. }
  1151. void DrawPinShape(const ImVec2& pin_pos, const ImPinData& pin, const ImU32 pin_color)
  1152. {
  1153. static const int CIRCLE_NUM_SEGMENTS = 8;
  1154. switch (pin.Shape)
  1155. {
  1156. case ImNodesPinShape_Circle:
  1157. {
  1158. GImNodes->CanvasDrawList->AddCircle(
  1159. pin_pos,
  1160. GImNodes->Style.PinCircleRadius,
  1161. pin_color,
  1162. CIRCLE_NUM_SEGMENTS,
  1163. GImNodes->Style.PinLineThickness);
  1164. }
  1165. break;
  1166. case ImNodesPinShape_CircleFilled:
  1167. {
  1168. GImNodes->CanvasDrawList->AddCircleFilled(
  1169. pin_pos, GImNodes->Style.PinCircleRadius, pin_color, CIRCLE_NUM_SEGMENTS);
  1170. }
  1171. break;
  1172. case ImNodesPinShape_Quad:
  1173. {
  1174. const QuadOffsets offset = CalculateQuadOffsets(GImNodes->Style.PinQuadSideLength);
  1175. GImNodes->CanvasDrawList->AddQuad(
  1176. pin_pos + offset.TopLeft,
  1177. pin_pos + offset.BottomLeft,
  1178. pin_pos + offset.BottomRight,
  1179. pin_pos + offset.TopRight,
  1180. pin_color,
  1181. GImNodes->Style.PinLineThickness);
  1182. }
  1183. break;
  1184. case ImNodesPinShape_QuadFilled:
  1185. {
  1186. const QuadOffsets offset = CalculateQuadOffsets(GImNodes->Style.PinQuadSideLength);
  1187. GImNodes->CanvasDrawList->AddQuadFilled(
  1188. pin_pos + offset.TopLeft,
  1189. pin_pos + offset.BottomLeft,
  1190. pin_pos + offset.BottomRight,
  1191. pin_pos + offset.TopRight,
  1192. pin_color);
  1193. }
  1194. break;
  1195. case ImNodesPinShape_Triangle:
  1196. {
  1197. const TriangleOffsets offset =
  1198. CalculateTriangleOffsets(GImNodes->Style.PinTriangleSideLength);
  1199. GImNodes->CanvasDrawList->AddTriangle(
  1200. pin_pos + offset.TopLeft,
  1201. pin_pos + offset.BottomLeft,
  1202. pin_pos + offset.Right,
  1203. pin_color,
  1204. // NOTE: for some weird reason, the line drawn by AddTriangle is
  1205. // much thinner than the lines drawn by AddCircle or AddQuad.
  1206. // Multiplying the line thickness by two seemed to solve the
  1207. // problem at a few different thickness values.
  1208. 2.f * GImNodes->Style.PinLineThickness);
  1209. }
  1210. break;
  1211. case ImNodesPinShape_TriangleFilled:
  1212. {
  1213. const TriangleOffsets offset =
  1214. CalculateTriangleOffsets(GImNodes->Style.PinTriangleSideLength);
  1215. GImNodes->CanvasDrawList->AddTriangleFilled(
  1216. pin_pos + offset.TopLeft,
  1217. pin_pos + offset.BottomLeft,
  1218. pin_pos + offset.Right,
  1219. pin_color);
  1220. }
  1221. break;
  1222. default:
  1223. assert(!"Invalid PinShape value!");
  1224. break;
  1225. }
  1226. }
  1227. void DrawPin(ImNodesEditorContext& editor, const int pin_idx)
  1228. {
  1229. ImPinData& pin = editor.Pins.Pool[pin_idx];
  1230. const ImRect& parent_node_rect = editor.Nodes.Pool[pin.ParentNodeIdx].Rect;
  1231. pin.Pos = GetScreenSpacePinCoordinates(parent_node_rect, pin.AttributeRect, pin.Type);
  1232. ImU32 pin_color = pin.ColorStyle.Background;
  1233. if (GImNodes->HoveredPinIdx == pin_idx)
  1234. {
  1235. pin_color = pin.ColorStyle.Hovered;
  1236. }
  1237. DrawPinShape(pin.Pos, pin, pin_color);
  1238. }
  1239. void DrawNode(ImNodesEditorContext& editor, const int node_idx)
  1240. {
  1241. const ImNodeData& node = editor.Nodes.Pool[node_idx];
  1242. ImGui::SetCursorPos(node.Origin + editor.Panning);
  1243. const bool node_hovered =
  1244. GImNodes->HoveredNodeIdx == node_idx &&
  1245. editor.ClickInteraction.Type != ImNodesClickInteractionType_BoxSelection;
  1246. ImU32 node_background = node.ColorStyle.Background;
  1247. ImU32 titlebar_background = node.ColorStyle.Titlebar;
  1248. if (editor.SelectedNodeIndices.contains(node_idx))
  1249. {
  1250. node_background = node.ColorStyle.BackgroundSelected;
  1251. titlebar_background = node.ColorStyle.TitlebarSelected;
  1252. }
  1253. else if (node_hovered)
  1254. {
  1255. node_background = node.ColorStyle.BackgroundHovered;
  1256. titlebar_background = node.ColorStyle.TitlebarHovered;
  1257. }
  1258. {
  1259. // node base
  1260. GImNodes->CanvasDrawList->AddRectFilled(
  1261. node.Rect.Min, node.Rect.Max, node_background, node.LayoutStyle.CornerRounding);
  1262. // title bar:
  1263. if (node.TitleBarContentRect.GetHeight() > 0.f)
  1264. {
  1265. ImRect title_bar_rect = GetNodeTitleRect(node);
  1266. #if IMGUI_VERSION_NUM < 18200
  1267. GImNodes->CanvasDrawList->AddRectFilled(
  1268. title_bar_rect.Min,
  1269. title_bar_rect.Max,
  1270. titlebar_background,
  1271. node.LayoutStyle.CornerRounding,
  1272. ImDrawCornerFlags_Top);
  1273. #else
  1274. GImNodes->CanvasDrawList->AddRectFilled(
  1275. title_bar_rect.Min,
  1276. title_bar_rect.Max,
  1277. titlebar_background,
  1278. node.LayoutStyle.CornerRounding,
  1279. ImDrawFlags_RoundCornersTop);
  1280. #endif
  1281. }
  1282. if ((GImNodes->Style.Flags & ImNodesStyleFlags_NodeOutline) != 0)
  1283. {
  1284. #if IMGUI_VERSION_NUM < 18200
  1285. GImNodes->CanvasDrawList->AddRect(
  1286. node.Rect.Min,
  1287. node.Rect.Max,
  1288. node.ColorStyle.Outline,
  1289. node.LayoutStyle.CornerRounding,
  1290. ImDrawCornerFlags_All,
  1291. node.LayoutStyle.BorderThickness);
  1292. #else
  1293. GImNodes->CanvasDrawList->AddRect(
  1294. node.Rect.Min,
  1295. node.Rect.Max,
  1296. node.ColorStyle.Outline,
  1297. node.LayoutStyle.CornerRounding,
  1298. ImDrawFlags_RoundCornersAll,
  1299. node.LayoutStyle.BorderThickness);
  1300. #endif
  1301. }
  1302. }
  1303. for (int i = 0; i < node.PinIndices.size(); ++i)
  1304. {
  1305. DrawPin(editor, node.PinIndices[i]);
  1306. }
  1307. if (node_hovered)
  1308. {
  1309. GImNodes->HoveredNodeIdx = node_idx;
  1310. }
  1311. }
  1312. void DrawLink(ImNodesEditorContext& editor, const int link_idx)
  1313. {
  1314. const ImLinkData& link = editor.Links.Pool[link_idx];
  1315. const ImPinData& start_pin = editor.Pins.Pool[link.StartPinIdx];
  1316. const ImPinData& end_pin = editor.Pins.Pool[link.EndPinIdx];
  1317. const CubicBezier cubic_bezier = GetCubicBezier(
  1318. start_pin.Pos, end_pin.Pos, start_pin.Type, GImNodes->Style.LinkLineSegmentsPerLength);
  1319. const bool link_hovered =
  1320. GImNodes->HoveredLinkIdx == link_idx &&
  1321. editor.ClickInteraction.Type != ImNodesClickInteractionType_BoxSelection;
  1322. if (link_hovered)
  1323. {
  1324. GImNodes->HoveredLinkIdx = link_idx;
  1325. }
  1326. // It's possible for a link to be deleted in begin_link_interaction. A user
  1327. // may detach a link, resulting in the link wire snapping to the mouse
  1328. // position.
  1329. //
  1330. // In other words, skip rendering the link if it was deleted.
  1331. if (GImNodes->DeletedLinkIdx == link_idx)
  1332. {
  1333. return;
  1334. }
  1335. ImU32 link_color = link.ColorStyle.Base;
  1336. if (editor.SelectedLinkIndices.contains(link_idx))
  1337. {
  1338. link_color = link.ColorStyle.Selected;
  1339. }
  1340. else if (link_hovered)
  1341. {
  1342. link_color = link.ColorStyle.Hovered;
  1343. }
  1344. #if IMGUI_VERSION_NUM < 18000
  1345. GImNodes->CanvasDrawList->AddBezierCurve(
  1346. #else
  1347. GImNodes->CanvasDrawList->AddBezierCubic(
  1348. #endif
  1349. cubic_bezier.P0,
  1350. cubic_bezier.P1,
  1351. cubic_bezier.P2,
  1352. cubic_bezier.P3,
  1353. link_color,
  1354. GImNodes->Style.LinkThickness,
  1355. cubic_bezier.NumSegments);
  1356. }
  1357. void BeginPinAttribute(
  1358. const int id,
  1359. const ImNodesAttributeType type,
  1360. const ImNodesPinShape shape,
  1361. const int node_idx)
  1362. {
  1363. // Make sure to call BeginNode() before calling
  1364. // BeginAttribute()
  1365. assert(GImNodes->CurrentScope == ImNodesScope_Node);
  1366. GImNodes->CurrentScope = ImNodesScope_Attribute;
  1367. ImGui::BeginGroup();
  1368. ImGui::PushID(id);
  1369. ImNodesEditorContext& editor = EditorContextGet();
  1370. GImNodes->CurrentAttributeId = id;
  1371. const int pin_idx = ObjectPoolFindOrCreateIndex(editor.Pins, id);
  1372. GImNodes->CurrentPinIdx = pin_idx;
  1373. ImPinData& pin = editor.Pins.Pool[pin_idx];
  1374. pin.Id = id;
  1375. pin.ParentNodeIdx = node_idx;
  1376. pin.Type = type;
  1377. pin.Shape = shape;
  1378. pin.Flags = GImNodes->CurrentAttributeFlags;
  1379. pin.ColorStyle.Background = GImNodes->Style.Colors[ImNodesCol_Pin];
  1380. pin.ColorStyle.Hovered = GImNodes->Style.Colors[ImNodesCol_PinHovered];
  1381. }
  1382. void EndPinAttribute()
  1383. {
  1384. assert(GImNodes->CurrentScope == ImNodesScope_Attribute);
  1385. GImNodes->CurrentScope = ImNodesScope_Node;
  1386. ImGui::PopID();
  1387. ImGui::EndGroup();
  1388. if (ImGui::IsItemActive())
  1389. {
  1390. GImNodes->ActiveAttribute = true;
  1391. GImNodes->ActiveAttributeId = GImNodes->CurrentAttributeId;
  1392. }
  1393. ImNodesEditorContext& editor = EditorContextGet();
  1394. ImPinData& pin = editor.Pins.Pool[GImNodes->CurrentPinIdx];
  1395. ImNodeData& node = editor.Nodes.Pool[GImNodes->CurrentNodeIdx];
  1396. pin.AttributeRect = GetItemRect();
  1397. node.PinIndices.push_back(GImNodes->CurrentPinIdx);
  1398. }
  1399. void Initialize(ImNodesContext* context)
  1400. {
  1401. context->CanvasOriginScreenSpace = ImVec2(0.0f, 0.0f);
  1402. context->CanvasRectScreenSpace = ImRect(ImVec2(0.f, 0.f), ImVec2(0.f, 0.f));
  1403. context->CurrentScope = ImNodesScope_None;
  1404. context->MiniMapRectScreenSpace = ImRect(ImVec2(0.f, 0.f), ImVec2(0.f, 0.f));
  1405. context->MiniMapRectSnappingOffset = ImVec2(0.f, 0.f);
  1406. context->MiniMapZoom = 0.1f;
  1407. context->MiniMapNodeHoveringCallback = NULL;
  1408. context->MiniMapNodeHoveringCallbackUserData = NULL;
  1409. context->CurrentPinIdx = INT_MAX;
  1410. context->CurrentNodeIdx = INT_MAX;
  1411. context->DefaultEditorCtx = EditorContextCreate();
  1412. EditorContextSet(GImNodes->DefaultEditorCtx);
  1413. context->CurrentAttributeFlags = ImNodesAttributeFlags_None;
  1414. context->AttributeFlagStack.push_back(GImNodes->CurrentAttributeFlags);
  1415. StyleColorsDark();
  1416. }
  1417. void Shutdown(ImNodesContext* ctx) { EditorContextFree(ctx->DefaultEditorCtx); }
  1418. // [SECTION] minimap
  1419. static inline bool IsMiniMapActive() { return GImNodes->MiniMapRectScreenSpace.GetWidth() > 0.f; }
  1420. static inline bool IsMiniMapHovered()
  1421. {
  1422. return IsMiniMapActive() &&
  1423. ImGui::IsMouseHoveringRect(
  1424. GImNodes->MiniMapRectScreenSpace.Min, GImNodes->MiniMapRectScreenSpace.Max);
  1425. }
  1426. static inline ImRect ToMiniMapRect(
  1427. const float minimap_size_fraction,
  1428. const ImRect& editor_rect,
  1429. const ImNodesMiniMapLocation location)
  1430. {
  1431. const ImVec2 editor_size(editor_rect.Max - editor_rect.Min);
  1432. const float max_editor_coord = fmaxf(editor_size.x, editor_size.y);
  1433. const float mini_map_coord = minimap_size_fraction * max_editor_coord;
  1434. const float corner_offset_alpha = fminf(1.f - minimap_size_fraction, 0.1f);
  1435. const float corner_offset_coord = corner_offset_alpha * mini_map_coord;
  1436. // Compute the size of the mini-map area; lower bound with some reasonable size values
  1437. const ImVec2 mini_map_size(mini_map_coord, mini_map_coord);
  1438. // Corner offset from editor context
  1439. const ImVec2 corner_offset(corner_offset_coord, corner_offset_coord);
  1440. switch (location)
  1441. {
  1442. case ImNodesMiniMapLocation_BottomRight:
  1443. return ImRect(
  1444. editor_rect.Max - corner_offset - mini_map_size, editor_rect.Max - corner_offset);
  1445. case ImNodesMiniMapLocation_BottomLeft:
  1446. return ImRect(
  1447. ImVec2(
  1448. editor_rect.Min.x + corner_offset.x,
  1449. editor_rect.Max.y - corner_offset.y - mini_map_size.y),
  1450. ImVec2(
  1451. editor_rect.Min.x + corner_offset.x + mini_map_size.x,
  1452. editor_rect.Max.y - corner_offset.y));
  1453. case ImNodesMiniMapLocation_TopRight:
  1454. return ImRect(
  1455. ImVec2(
  1456. editor_rect.Max.x - corner_offset.x - mini_map_size.x,
  1457. editor_rect.Min.y + corner_offset.y),
  1458. ImVec2(
  1459. editor_rect.Max.x - corner_offset.x,
  1460. editor_rect.Min.y + corner_offset.y + mini_map_size.y));
  1461. case ImNodesMiniMapLocation_TopLeft:
  1462. // [[fallthrough]]
  1463. default:
  1464. // [[fallthrough]]
  1465. break;
  1466. }
  1467. return ImRect(editor_rect.Min + corner_offset, editor_rect.Min + corner_offset + mini_map_size);
  1468. }
  1469. static void MiniMapDrawNode(
  1470. ImNodesEditorContext& editor,
  1471. const int node_idx,
  1472. const ImVec2& editor_center,
  1473. const ImVec2& mini_map_center,
  1474. const float scaling)
  1475. {
  1476. const ImNodeData& node = editor.Nodes.Pool[node_idx];
  1477. const ImVec2 editor_node_offset(node.Rect.Min - editor_center);
  1478. const ImVec2 mini_map_node_size((node.Rect.Max - node.Rect.Min) * scaling);
  1479. const ImVec2 mini_map_node_min(editor_node_offset * scaling + mini_map_center);
  1480. const ImVec2 mini_map_node_max(mini_map_node_min + mini_map_node_size);
  1481. // Round to near whole pixel value for corner-rounding to prevent visual glitches
  1482. const float mini_map_node_rounding = floorf(node.LayoutStyle.CornerRounding * scaling);
  1483. ImU32 mini_map_node_background;
  1484. if (editor.ClickInteraction.Type == ImNodesClickInteractionType_None &&
  1485. ImGui::IsMouseHoveringRect(mini_map_node_min, mini_map_node_max))
  1486. {
  1487. mini_map_node_background = GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundHovered];
  1488. // Run user callback when hovering a mini-map node
  1489. if (GImNodes->MiniMapNodeHoveringCallback)
  1490. {
  1491. GImNodes->MiniMapNodeHoveringCallback(
  1492. node.Id, GImNodes->MiniMapNodeHoveringCallbackUserData);
  1493. }
  1494. // Compute the amount to pan editor to center node selected in the minimap
  1495. GImNodes->MiniMapRectSnappingOffset =
  1496. editor_center - (node.Rect.Min + node.Rect.Max) * 0.5f;
  1497. }
  1498. else if (editor.SelectedNodeIndices.contains(node_idx))
  1499. {
  1500. mini_map_node_background = GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundSelected];
  1501. }
  1502. else
  1503. {
  1504. mini_map_node_background = GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackground];
  1505. }
  1506. const ImU32 mini_map_node_outline = GImNodes->Style.Colors[ImNodesCol_MiniMapNodeOutline];
  1507. GImNodes->CanvasDrawList->AddRectFilled(
  1508. mini_map_node_min, mini_map_node_max, mini_map_node_background, mini_map_node_rounding);
  1509. GImNodes->CanvasDrawList->AddRect(
  1510. mini_map_node_min, mini_map_node_max, mini_map_node_outline, mini_map_node_rounding);
  1511. }
  1512. static void MiniMapDrawLink(
  1513. ImNodesEditorContext& editor,
  1514. const int link_idx,
  1515. const ImVec2& editor_center,
  1516. const ImVec2& mini_map_center,
  1517. const float scaling)
  1518. {
  1519. const ImLinkData& link = editor.Links.Pool[link_idx];
  1520. const ImPinData& start_pin = editor.Pins.Pool[link.StartPinIdx];
  1521. const ImPinData& end_pin = editor.Pins.Pool[link.EndPinIdx];
  1522. const CubicBezier cubic_bezier = GetCubicBezier(
  1523. (start_pin.Pos - editor_center) * scaling + mini_map_center,
  1524. (end_pin.Pos - editor_center) * scaling + mini_map_center,
  1525. start_pin.Type,
  1526. GImNodes->Style.LinkLineSegmentsPerLength / scaling);
  1527. // It's possible for a link to be deleted in begin_link_interaction. A user
  1528. // may detach a link, resulting in the link wire snapping to the mouse
  1529. // position.
  1530. //
  1531. // In other words, skip rendering the link if it was deleted.
  1532. if (GImNodes->DeletedLinkIdx == link_idx)
  1533. {
  1534. return;
  1535. }
  1536. const ImU32 link_color =
  1537. GImNodes->Style.Colors
  1538. [editor.SelectedLinkIndices.contains(link_idx) ? ImNodesCol_MiniMapLinkSelected
  1539. : ImNodesCol_MiniMapLink];
  1540. #if IMGUI_VERSION_NUM < 18000
  1541. GImNodes->CanvasDrawList->AddBezierCurve(
  1542. #else
  1543. GImNodes->CanvasDrawList->AddBezierCubic(
  1544. #endif
  1545. cubic_bezier.P0,
  1546. cubic_bezier.P1,
  1547. cubic_bezier.P2,
  1548. cubic_bezier.P3,
  1549. link_color,
  1550. GImNodes->Style.LinkThickness * scaling,
  1551. cubic_bezier.NumSegments);
  1552. }
  1553. static void MiniMapUpdate()
  1554. {
  1555. ImNodesEditorContext& editor = EditorContextGet();
  1556. ImU32 mini_map_background;
  1557. // NOTE: use normal background when panning (typically opaque)
  1558. if (editor.ClickInteraction.Type != ImNodesClickInteractionType_MiniMapPanning &&
  1559. IsMiniMapHovered())
  1560. {
  1561. mini_map_background = GImNodes->Style.Colors[ImNodesCol_MiniMapBackgroundHovered];
  1562. }
  1563. else
  1564. {
  1565. mini_map_background = GImNodes->Style.Colors[ImNodesCol_MiniMapBackground];
  1566. }
  1567. const ImRect& editor_rect = GImNodes->CanvasRectScreenSpace;
  1568. const ImVec2 editor_center(
  1569. 0.5f * (editor_rect.Min.x + editor_rect.Max.x),
  1570. 0.5f * (editor_rect.Min.y + editor_rect.Max.y));
  1571. const ImRect& mini_map_rect = GImNodes->MiniMapRectScreenSpace;
  1572. const ImVec2 mini_map_center(
  1573. 0.5f * (mini_map_rect.Min.x + mini_map_rect.Max.x),
  1574. 0.5f * (mini_map_rect.Min.y + mini_map_rect.Max.y));
  1575. // Draw minimap background and border
  1576. GImNodes->CanvasDrawList->AddRectFilled(
  1577. mini_map_rect.Min, mini_map_rect.Max, mini_map_background);
  1578. GImNodes->CanvasDrawList->AddRect(
  1579. mini_map_rect.Min, mini_map_rect.Max, GImNodes->Style.Colors[ImNodesCol_MiniMapOutline]);
  1580. // Clip draw list items to mini-map rect (after drawing background/outline)
  1581. GImNodes->CanvasDrawList->PushClipRect(
  1582. mini_map_rect.Min, mini_map_rect.Max, true /* intersect with editor clip-rect */);
  1583. // Get zoom scaling (0, 1]
  1584. const float scaling = GImNodes->MiniMapZoom;
  1585. // Draw links first so they appear under nodes, and we can use the same draw channel
  1586. for (int link_idx = 0; link_idx < editor.Links.Pool.size(); ++link_idx)
  1587. {
  1588. if (editor.Links.InUse[link_idx])
  1589. {
  1590. MiniMapDrawLink(editor, link_idx, editor_center, mini_map_center, scaling);
  1591. }
  1592. }
  1593. for (int node_idx = 0; node_idx < editor.Nodes.Pool.size(); ++node_idx)
  1594. {
  1595. if (editor.Nodes.InUse[node_idx])
  1596. {
  1597. MiniMapDrawNode(editor, node_idx, editor_center, mini_map_center, scaling);
  1598. }
  1599. }
  1600. // Have to pop mini-map clip rect
  1601. GImNodes->CanvasDrawList->PopClipRect();
  1602. // Reset callback info after use
  1603. GImNodes->MiniMapNodeHoveringCallback = NULL;
  1604. GImNodes->MiniMapNodeHoveringCallbackUserData = NULL;
  1605. // Reset mini-map area so that it will disappear if MiniMap(...) is not called on the next frame
  1606. GImNodes->MiniMapRectScreenSpace = ImRect(ImVec2(0.f, 0.f), ImVec2(0.f, 0.f));
  1607. }
  1608. // [SECTION] selection helpers
  1609. template<typename T>
  1610. void SelectObject(const ImObjectPool<T>& objects, ImVector<int>& selected_indices, const int id)
  1611. {
  1612. const int idx = ObjectPoolFind(objects, id);
  1613. assert(idx >= 0);
  1614. assert(selected_indices.find(idx) == selected_indices.end());
  1615. selected_indices.push_back(idx);
  1616. }
  1617. template<typename T>
  1618. void ClearObjectSelection(
  1619. const ImObjectPool<T>& objects,
  1620. ImVector<int>& selected_indices,
  1621. const int id)
  1622. {
  1623. const int idx = ObjectPoolFind(objects, id);
  1624. assert(idx >= 0);
  1625. assert(selected_indices.find(idx) != selected_indices.end());
  1626. selected_indices.find_erase_unsorted(idx);
  1627. }
  1628. template<typename T>
  1629. bool IsObjectSelected(const ImObjectPool<T>& objects, ImVector<int>& selected_indices, const int id)
  1630. {
  1631. const int idx = ObjectPoolFind(objects, id);
  1632. return selected_indices.find(idx) != selected_indices.end();
  1633. }
  1634. } // namespace
  1635. } // namespace ImNodes
  1636. // [SECTION] API implementation
  1637. ImNodesIO::EmulateThreeButtonMouse::EmulateThreeButtonMouse() : Modifier(NULL) {}
  1638. ImNodesIO::LinkDetachWithModifierClick::LinkDetachWithModifierClick() : Modifier(NULL) {}
  1639. ImNodesIO::ImNodesIO()
  1640. : EmulateThreeButtonMouse(), LinkDetachWithModifierClick(),
  1641. AltMouseButton(ImGuiMouseButton_Middle)
  1642. {
  1643. }
  1644. ImNodesStyle::ImNodesStyle()
  1645. : GridSpacing(32.f), NodeCornerRounding(4.f), NodePaddingHorizontal(8.f),
  1646. NodePaddingVertical(8.f), NodeBorderThickness(1.f), LinkThickness(3.f),
  1647. LinkLineSegmentsPerLength(0.1f), LinkHoverDistance(10.f), PinCircleRadius(4.f),
  1648. PinQuadSideLength(7.f), PinTriangleSideLength(9.5), PinLineThickness(1.f),
  1649. PinHoverRadius(10.f), PinOffset(0.f),
  1650. Flags(ImNodesStyleFlags_NodeOutline | ImNodesStyleFlags_GridLines), Colors()
  1651. {
  1652. }
  1653. namespace ImNodes
  1654. {
  1655. ImNodesContext* CreateContext()
  1656. {
  1657. ImNodesContext* ctx = IM_NEW(ImNodesContext)();
  1658. if (GImNodes == NULL)
  1659. SetCurrentContext(ctx);
  1660. Initialize(ctx);
  1661. return ctx;
  1662. }
  1663. void DestroyContext(ImNodesContext* ctx)
  1664. {
  1665. if (ctx == NULL)
  1666. ctx = GImNodes;
  1667. Shutdown(ctx);
  1668. if (GImNodes == ctx)
  1669. SetCurrentContext(NULL);
  1670. IM_DELETE(ctx);
  1671. }
  1672. ImNodesContext* GetCurrentContext() { return GImNodes; }
  1673. void SetCurrentContext(ImNodesContext* ctx) { GImNodes = ctx; }
  1674. ImNodesEditorContext* EditorContextCreate()
  1675. {
  1676. void* mem = ImGui::MemAlloc(sizeof(ImNodesEditorContext));
  1677. new (mem) ImNodesEditorContext();
  1678. return (ImNodesEditorContext*)mem;
  1679. }
  1680. void EditorContextFree(ImNodesEditorContext* ctx)
  1681. {
  1682. ctx->~ImNodesEditorContext();
  1683. ImGui::MemFree(ctx);
  1684. }
  1685. void EditorContextSet(ImNodesEditorContext* ctx) { GImNodes->EditorCtx = ctx; }
  1686. ImVec2 EditorContextGetPanning()
  1687. {
  1688. const ImNodesEditorContext& editor = EditorContextGet();
  1689. return editor.Panning;
  1690. }
  1691. void EditorContextResetPanning(const ImVec2& pos)
  1692. {
  1693. ImNodesEditorContext& editor = EditorContextGet();
  1694. editor.Panning = pos;
  1695. }
  1696. void EditorContextMoveToNode(const int node_id)
  1697. {
  1698. ImNodesEditorContext& editor = EditorContextGet();
  1699. ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id);
  1700. editor.Panning.x = -node.Origin.x;
  1701. editor.Panning.y = -node.Origin.y;
  1702. }
  1703. void SetImGuiContext(ImGuiContext* ctx) { ImGui::SetCurrentContext(ctx); }
  1704. ImNodesIO& GetIO() { return GImNodes->Io; }
  1705. ImNodesStyle& GetStyle() { return GImNodes->Style; }
  1706. void StyleColorsDark()
  1707. {
  1708. GImNodes->Style.Colors[ImNodesCol_NodeBackground] = IM_COL32(50, 50, 50, 255);
  1709. GImNodes->Style.Colors[ImNodesCol_NodeBackgroundHovered] = IM_COL32(75, 75, 75, 255);
  1710. GImNodes->Style.Colors[ImNodesCol_NodeBackgroundSelected] = IM_COL32(75, 75, 75, 255);
  1711. GImNodes->Style.Colors[ImNodesCol_NodeOutline] = IM_COL32(100, 100, 100, 255);
  1712. // title bar colors match ImGui's titlebg colors
  1713. GImNodes->Style.Colors[ImNodesCol_TitleBar] = IM_COL32(41, 74, 122, 255);
  1714. GImNodes->Style.Colors[ImNodesCol_TitleBarHovered] = IM_COL32(66, 150, 250, 255);
  1715. GImNodes->Style.Colors[ImNodesCol_TitleBarSelected] = IM_COL32(66, 150, 250, 255);
  1716. // link colors match ImGui's slider grab colors
  1717. GImNodes->Style.Colors[ImNodesCol_Link] = IM_COL32(61, 133, 224, 200);
  1718. GImNodes->Style.Colors[ImNodesCol_LinkHovered] = IM_COL32(66, 150, 250, 255);
  1719. GImNodes->Style.Colors[ImNodesCol_LinkSelected] = IM_COL32(66, 150, 250, 255);
  1720. // pin colors match ImGui's button colors
  1721. GImNodes->Style.Colors[ImNodesCol_Pin] = IM_COL32(53, 150, 250, 180);
  1722. GImNodes->Style.Colors[ImNodesCol_PinHovered] = IM_COL32(53, 150, 250, 255);
  1723. GImNodes->Style.Colors[ImNodesCol_BoxSelector] = IM_COL32(61, 133, 224, 30);
  1724. GImNodes->Style.Colors[ImNodesCol_BoxSelectorOutline] = IM_COL32(61, 133, 224, 150);
  1725. GImNodes->Style.Colors[ImNodesCol_GridBackground] = IM_COL32(40, 40, 50, 200);
  1726. GImNodes->Style.Colors[ImNodesCol_GridLine] = IM_COL32(200, 200, 200, 40);
  1727. // minimap colors
  1728. GImNodes->Style.Colors[ImNodesCol_MiniMapBackground] = IM_COL32(25, 25, 25, 100);
  1729. GImNodes->Style.Colors[ImNodesCol_MiniMapBackgroundHovered] = IM_COL32(25, 25, 25, 200);
  1730. GImNodes->Style.Colors[ImNodesCol_MiniMapOutline] = IM_COL32(150, 150, 150, 100);
  1731. GImNodes->Style.Colors[ImNodesCol_MiniMapOutlineHovered] = IM_COL32(150, 150, 150, 200);
  1732. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackground] = IM_COL32(200, 200, 200, 100);
  1733. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundHovered] = IM_COL32(200, 200, 200, 255);
  1734. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundSelected] =
  1735. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundHovered];
  1736. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeOutline] = IM_COL32(200, 200, 200, 100);
  1737. GImNodes->Style.Colors[ImNodesCol_MiniMapLink] = GImNodes->Style.Colors[ImNodesCol_Link];
  1738. GImNodes->Style.Colors[ImNodesCol_MiniMapLinkSelected] =
  1739. GImNodes->Style.Colors[ImNodesCol_LinkSelected];
  1740. }
  1741. void StyleColorsClassic()
  1742. {
  1743. GImNodes->Style.Colors[ImNodesCol_NodeBackground] = IM_COL32(50, 50, 50, 255);
  1744. GImNodes->Style.Colors[ImNodesCol_NodeBackgroundHovered] = IM_COL32(75, 75, 75, 255);
  1745. GImNodes->Style.Colors[ImNodesCol_NodeBackgroundSelected] = IM_COL32(75, 75, 75, 255);
  1746. GImNodes->Style.Colors[ImNodesCol_NodeOutline] = IM_COL32(100, 100, 100, 255);
  1747. GImNodes->Style.Colors[ImNodesCol_TitleBar] = IM_COL32(69, 69, 138, 255);
  1748. GImNodes->Style.Colors[ImNodesCol_TitleBarHovered] = IM_COL32(82, 82, 161, 255);
  1749. GImNodes->Style.Colors[ImNodesCol_TitleBarSelected] = IM_COL32(82, 82, 161, 255);
  1750. GImNodes->Style.Colors[ImNodesCol_Link] = IM_COL32(255, 255, 255, 100);
  1751. GImNodes->Style.Colors[ImNodesCol_LinkHovered] = IM_COL32(105, 99, 204, 153);
  1752. GImNodes->Style.Colors[ImNodesCol_LinkSelected] = IM_COL32(105, 99, 204, 153);
  1753. GImNodes->Style.Colors[ImNodesCol_Pin] = IM_COL32(89, 102, 156, 170);
  1754. GImNodes->Style.Colors[ImNodesCol_PinHovered] = IM_COL32(102, 122, 179, 200);
  1755. GImNodes->Style.Colors[ImNodesCol_BoxSelector] = IM_COL32(82, 82, 161, 100);
  1756. GImNodes->Style.Colors[ImNodesCol_BoxSelectorOutline] = IM_COL32(82, 82, 161, 255);
  1757. GImNodes->Style.Colors[ImNodesCol_GridBackground] = IM_COL32(40, 40, 50, 200);
  1758. GImNodes->Style.Colors[ImNodesCol_GridLine] = IM_COL32(200, 200, 200, 40);
  1759. // minimap colors
  1760. GImNodes->Style.Colors[ImNodesCol_MiniMapBackground] = IM_COL32(25, 25, 25, 100);
  1761. GImNodes->Style.Colors[ImNodesCol_MiniMapBackgroundHovered] = IM_COL32(25, 25, 25, 200);
  1762. GImNodes->Style.Colors[ImNodesCol_MiniMapOutline] = IM_COL32(150, 150, 150, 100);
  1763. GImNodes->Style.Colors[ImNodesCol_MiniMapOutlineHovered] = IM_COL32(150, 150, 150, 200);
  1764. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackground] = IM_COL32(200, 200, 200, 100);
  1765. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundSelected] =
  1766. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundHovered];
  1767. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundSelected] = IM_COL32(200, 200, 240, 255);
  1768. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeOutline] = IM_COL32(200, 200, 200, 100);
  1769. GImNodes->Style.Colors[ImNodesCol_MiniMapLink] = GImNodes->Style.Colors[ImNodesCol_Link];
  1770. GImNodes->Style.Colors[ImNodesCol_MiniMapLinkSelected] =
  1771. GImNodes->Style.Colors[ImNodesCol_LinkSelected];
  1772. }
  1773. void StyleColorsLight()
  1774. {
  1775. GImNodes->Style.Colors[ImNodesCol_NodeBackground] = IM_COL32(240, 240, 240, 255);
  1776. GImNodes->Style.Colors[ImNodesCol_NodeBackgroundHovered] = IM_COL32(240, 240, 240, 255);
  1777. GImNodes->Style.Colors[ImNodesCol_NodeBackgroundSelected] = IM_COL32(240, 240, 240, 255);
  1778. GImNodes->Style.Colors[ImNodesCol_NodeOutline] = IM_COL32(100, 100, 100, 255);
  1779. GImNodes->Style.Colors[ImNodesCol_TitleBar] = IM_COL32(248, 248, 248, 255);
  1780. GImNodes->Style.Colors[ImNodesCol_TitleBarHovered] = IM_COL32(209, 209, 209, 255);
  1781. GImNodes->Style.Colors[ImNodesCol_TitleBarSelected] = IM_COL32(209, 209, 209, 255);
  1782. // original imgui values: 66, 150, 250
  1783. GImNodes->Style.Colors[ImNodesCol_Link] = IM_COL32(66, 150, 250, 100);
  1784. // original imgui values: 117, 138, 204
  1785. GImNodes->Style.Colors[ImNodesCol_LinkHovered] = IM_COL32(66, 150, 250, 242);
  1786. GImNodes->Style.Colors[ImNodesCol_LinkSelected] = IM_COL32(66, 150, 250, 242);
  1787. // original imgui values: 66, 150, 250
  1788. GImNodes->Style.Colors[ImNodesCol_Pin] = IM_COL32(66, 150, 250, 160);
  1789. GImNodes->Style.Colors[ImNodesCol_PinHovered] = IM_COL32(66, 150, 250, 255);
  1790. GImNodes->Style.Colors[ImNodesCol_BoxSelector] = IM_COL32(90, 170, 250, 30);
  1791. GImNodes->Style.Colors[ImNodesCol_BoxSelectorOutline] = IM_COL32(90, 170, 250, 150);
  1792. GImNodes->Style.Colors[ImNodesCol_GridBackground] = IM_COL32(225, 225, 225, 255);
  1793. GImNodes->Style.Colors[ImNodesCol_GridLine] = IM_COL32(180, 180, 180, 100);
  1794. // minimap colors
  1795. GImNodes->Style.Colors[ImNodesCol_MiniMapBackground] = IM_COL32(25, 25, 25, 100);
  1796. GImNodes->Style.Colors[ImNodesCol_MiniMapBackgroundHovered] = IM_COL32(25, 25, 25, 200);
  1797. GImNodes->Style.Colors[ImNodesCol_MiniMapOutline] = IM_COL32(150, 150, 150, 100);
  1798. GImNodes->Style.Colors[ImNodesCol_MiniMapOutlineHovered] = IM_COL32(150, 150, 150, 200);
  1799. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackground] = IM_COL32(200, 200, 200, 100);
  1800. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundSelected] =
  1801. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundHovered];
  1802. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeBackgroundSelected] = IM_COL32(200, 200, 240, 255);
  1803. GImNodes->Style.Colors[ImNodesCol_MiniMapNodeOutline] = IM_COL32(200, 200, 200, 100);
  1804. GImNodes->Style.Colors[ImNodesCol_MiniMapLink] = GImNodes->Style.Colors[ImNodesCol_Link];
  1805. GImNodes->Style.Colors[ImNodesCol_MiniMapLinkSelected] =
  1806. GImNodes->Style.Colors[ImNodesCol_LinkSelected];
  1807. }
  1808. void BeginNodeEditor()
  1809. {
  1810. assert(GImNodes->CurrentScope == ImNodesScope_None);
  1811. GImNodes->CurrentScope = ImNodesScope_Editor;
  1812. // Reset state from previous pass
  1813. ImNodesEditorContext& editor = EditorContextGet();
  1814. ObjectPoolReset(editor.Nodes);
  1815. ObjectPoolReset(editor.Pins);
  1816. ObjectPoolReset(editor.Links);
  1817. GImNodes->HoveredNodeIdx.Reset();
  1818. GImNodes->HoveredLinkIdx.Reset();
  1819. GImNodes->HoveredPinIdx.Reset();
  1820. GImNodes->DeletedLinkIdx.Reset();
  1821. GImNodes->SnapLinkIdx.Reset();
  1822. GImNodes->NodeIndicesOverlappingWithMouse.clear();
  1823. GImNodes->ImNodesUIState = ImNodesUIState_None;
  1824. GImNodes->MousePos = ImGui::GetIO().MousePos;
  1825. GImNodes->LeftMouseClicked = ImGui::IsMouseClicked(0);
  1826. GImNodes->LeftMouseReleased = ImGui::IsMouseReleased(0);
  1827. GImNodes->AltMouseClicked =
  1828. (GImNodes->Io.EmulateThreeButtonMouse.Modifier != NULL &&
  1829. *GImNodes->Io.EmulateThreeButtonMouse.Modifier && GImNodes->LeftMouseClicked) ||
  1830. ImGui::IsMouseClicked(GImNodes->Io.AltMouseButton);
  1831. GImNodes->LeftMouseDragging = ImGui::IsMouseDragging(0, 0.0f);
  1832. GImNodes->AltMouseDragging =
  1833. (GImNodes->Io.EmulateThreeButtonMouse.Modifier != NULL && GImNodes->LeftMouseDragging &&
  1834. (*GImNodes->Io.EmulateThreeButtonMouse.Modifier)) ||
  1835. ImGui::IsMouseDragging(GImNodes->Io.AltMouseButton, 0.0f);
  1836. GImNodes->AltMouseScrollDelta = ImGui::GetIO().MouseWheel;
  1837. GImNodes->ActiveAttribute = false;
  1838. ImGui::BeginGroup();
  1839. {
  1840. ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1.f, 1.f));
  1841. ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f));
  1842. ImGui::PushStyleColor(ImGuiCol_ChildBg, GImNodes->Style.Colors[ImNodesCol_GridBackground]);
  1843. ImGui::BeginChild(
  1844. "scrolling_region",
  1845. ImVec2(0.f, 0.f),
  1846. true,
  1847. ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove |
  1848. ImGuiWindowFlags_NoScrollWithMouse);
  1849. GImNodes->CanvasOriginScreenSpace = ImGui::GetCursorScreenPos();
  1850. // NOTE: we have to fetch the canvas draw list *after* we call
  1851. // BeginChild(), otherwise the ImGui UI elements are going to be
  1852. // rendered into the parent window draw list.
  1853. DrawListSet(ImGui::GetWindowDrawList());
  1854. {
  1855. const ImVec2 canvas_size = ImGui::GetWindowSize();
  1856. GImNodes->CanvasRectScreenSpace = ImRect(
  1857. EditorSpaceToScreenSpace(ImVec2(0.f, 0.f)), EditorSpaceToScreenSpace(canvas_size));
  1858. if (GImNodes->Style.Flags & ImNodesStyleFlags_GridLines)
  1859. {
  1860. DrawGrid(editor, canvas_size);
  1861. }
  1862. }
  1863. }
  1864. }
  1865. void EndNodeEditor()
  1866. {
  1867. assert(GImNodes->CurrentScope == ImNodesScope_Editor);
  1868. GImNodes->CurrentScope = ImNodesScope_None;
  1869. ImNodesEditorContext& editor = EditorContextGet();
  1870. // Detect ImGui interaction first, because it blocks interaction with the rest of the UI
  1871. if (GImNodes->LeftMouseClicked && ImGui::IsAnyItemActive())
  1872. {
  1873. editor.ClickInteraction.Type = ImNodesClickInteractionType_ImGuiItem;
  1874. }
  1875. // Detect which UI element is being hovered over. Detection is done in a hierarchical fashion,
  1876. // because a UI element being hovered excludes any other as being hovered over.
  1877. // Don't do hovering detection for nodes/links/pins when interacting with the mini-map, since
  1878. // its an *overlay* with its own interaction behavior and must have precedence during mouse
  1879. // interaction.
  1880. if ((editor.ClickInteraction.Type == ImNodesClickInteractionType_None ||
  1881. editor.ClickInteraction.Type == ImNodesClickInteractionType_LinkCreation) &&
  1882. MouseInCanvas() && !IsMiniMapHovered())
  1883. {
  1884. // Pins needs some special care. We need to check the depth stack to see which pins are
  1885. // being occluded by other nodes.
  1886. ResolveOccludedPins(editor, GImNodes->OccludedPinIndices);
  1887. GImNodes->HoveredPinIdx = ResolveHoveredPin(editor.Pins, GImNodes->OccludedPinIndices);
  1888. if (!GImNodes->HoveredPinIdx.HasValue())
  1889. {
  1890. // Resolve which node is actually on top and being hovered using the depth stack.
  1891. GImNodes->HoveredNodeIdx = ResolveHoveredNode(editor.NodeDepthOrder);
  1892. }
  1893. // We don't check for hovered pins here, because if we want to detach a link by clicking and
  1894. // dragging, we need to have both a link and pin hovered.
  1895. if (!GImNodes->HoveredNodeIdx.HasValue())
  1896. {
  1897. GImNodes->HoveredLinkIdx = ResolveHoveredLink(editor.Links, editor.Pins);
  1898. }
  1899. }
  1900. for (int node_idx = 0; node_idx < editor.Nodes.Pool.size(); ++node_idx)
  1901. {
  1902. if (editor.Nodes.InUse[node_idx])
  1903. {
  1904. DrawListActivateNodeBackground(node_idx);
  1905. DrawNode(editor, node_idx);
  1906. }
  1907. }
  1908. // In order to render the links underneath the nodes, we want to first select the bottom draw
  1909. // channel.
  1910. GImNodes->CanvasDrawList->ChannelsSetCurrent(0);
  1911. for (int link_idx = 0; link_idx < editor.Links.Pool.size(); ++link_idx)
  1912. {
  1913. if (editor.Links.InUse[link_idx])
  1914. {
  1915. DrawLink(editor, link_idx);
  1916. }
  1917. }
  1918. // Render the click interaction UI elements (partial links, box selector) on top of everything
  1919. // else.
  1920. DrawListAppendClickInteractionChannel();
  1921. DrawListActivateClickInteractionChannel();
  1922. // Handle node graph interaction
  1923. {
  1924. if (GImNodes->LeftMouseClicked && GImNodes->HoveredLinkIdx.HasValue())
  1925. {
  1926. BeginLinkInteraction(editor, GImNodes->HoveredLinkIdx.Value());
  1927. }
  1928. else if (GImNodes->LeftMouseClicked && GImNodes->HoveredPinIdx.HasValue())
  1929. {
  1930. BeginLinkCreation(editor, GImNodes->HoveredPinIdx.Value());
  1931. }
  1932. else if (GImNodes->LeftMouseClicked && GImNodes->HoveredNodeIdx.HasValue())
  1933. {
  1934. BeginNodeSelection(editor, GImNodes->HoveredNodeIdx.Value());
  1935. }
  1936. else if (
  1937. GImNodes->LeftMouseClicked || GImNodes->LeftMouseReleased ||
  1938. GImNodes->AltMouseClicked || GImNodes->AltMouseScrollDelta != 0.f)
  1939. {
  1940. BeginCanvasInteraction(editor);
  1941. }
  1942. ClickInteractionUpdate(editor);
  1943. }
  1944. // Mini-map rect will be set with non-zero width if MiniMap(...) was called
  1945. if (IsMiniMapActive())
  1946. {
  1947. MiniMapUpdate();
  1948. }
  1949. // At this point, draw commands have been issued for all nodes (and pins). Update the node pool
  1950. // to detect unused node slots and remove those indices from the depth stack before sorting the
  1951. // node draw commands by depth.
  1952. ObjectPoolUpdate(editor.Nodes);
  1953. ObjectPoolUpdate(editor.Pins);
  1954. DrawListSortChannelsByDepth(editor.NodeDepthOrder);
  1955. // After the links have been rendered, the link pool can be updated as well.
  1956. ObjectPoolUpdate(editor.Links);
  1957. // Finally, merge the draw channels
  1958. GImNodes->CanvasDrawList->ChannelsMerge();
  1959. // pop style
  1960. ImGui::EndChild(); // end scrolling region
  1961. ImGui::PopStyleColor(); // pop child window background color
  1962. ImGui::PopStyleVar(); // pop window padding
  1963. ImGui::PopStyleVar(); // pop frame padding
  1964. ImGui::EndGroup();
  1965. }
  1966. void MiniMap(
  1967. const float minimap_size_fraction,
  1968. const ImNodesMiniMapLocation location,
  1969. const ImNodesMiniMapNodeHoveringCallback node_hovering_callback,
  1970. void* node_hovering_callback_data)
  1971. {
  1972. // Check that editor size fraction is sane; must be in the range (0, 1]
  1973. assert(minimap_size_fraction > 0.f && minimap_size_fraction <= 1.f);
  1974. // Remember to call before EndNodeEditor
  1975. assert(GImNodes->CurrentScope == ImNodesScope_Editor);
  1976. // Set the size of the mini map to the global state
  1977. GImNodes->MiniMapRectScreenSpace =
  1978. ToMiniMapRect(minimap_size_fraction, GImNodes->CanvasRectScreenSpace, location);
  1979. // We'll know that the mini map is active if GImNodes->MiniMapRectScreenSpace specifies
  1980. // a non-zero area (actually, just the width is checked for non-zero size)
  1981. // Set node hovering callback information
  1982. GImNodes->MiniMapNodeHoveringCallback = node_hovering_callback;
  1983. GImNodes->MiniMapNodeHoveringCallbackUserData = node_hovering_callback_data;
  1984. // Actual drawing/updating of the MiniMap is done in EndNodeEditor so that
  1985. // mini map is draw over everything and all pin/link positions are updated
  1986. // correctly relative to their respective nodes. Hence, we must store some of
  1987. // of the state for the mini map in GImNodes for the actual drawing/updating
  1988. }
  1989. void BeginNode(const int node_id)
  1990. {
  1991. // Remember to call BeginNodeEditor before calling BeginNode
  1992. assert(GImNodes->CurrentScope == ImNodesScope_Editor);
  1993. GImNodes->CurrentScope = ImNodesScope_Node;
  1994. ImNodesEditorContext& editor = EditorContextGet();
  1995. const int node_idx = ObjectPoolFindOrCreateIndex(editor.Nodes, node_id);
  1996. GImNodes->CurrentNodeIdx = node_idx;
  1997. ImNodeData& node = editor.Nodes.Pool[node_idx];
  1998. node.ColorStyle.Background = GImNodes->Style.Colors[ImNodesCol_NodeBackground];
  1999. node.ColorStyle.BackgroundHovered = GImNodes->Style.Colors[ImNodesCol_NodeBackgroundHovered];
  2000. node.ColorStyle.BackgroundSelected = GImNodes->Style.Colors[ImNodesCol_NodeBackgroundSelected];
  2001. node.ColorStyle.Outline = GImNodes->Style.Colors[ImNodesCol_NodeOutline];
  2002. node.ColorStyle.Titlebar = GImNodes->Style.Colors[ImNodesCol_TitleBar];
  2003. node.ColorStyle.TitlebarHovered = GImNodes->Style.Colors[ImNodesCol_TitleBarHovered];
  2004. node.ColorStyle.TitlebarSelected = GImNodes->Style.Colors[ImNodesCol_TitleBarSelected];
  2005. node.LayoutStyle.CornerRounding = GImNodes->Style.NodeCornerRounding;
  2006. node.LayoutStyle.Padding =
  2007. ImVec2(GImNodes->Style.NodePaddingHorizontal, GImNodes->Style.NodePaddingVertical);
  2008. node.LayoutStyle.BorderThickness = GImNodes->Style.NodeBorderThickness;
  2009. // ImGui::SetCursorPos sets the cursor position, local to the current widget
  2010. // (in this case, the child object started in BeginNodeEditor). Use
  2011. // ImGui::SetCursorScreenPos to set the screen space coordinates directly.
  2012. ImGui::SetCursorPos(GridSpaceToEditorSpace(editor, GetNodeTitleBarOrigin(node)));
  2013. DrawListAddNode(node_idx);
  2014. DrawListActivateCurrentNodeForeground();
  2015. ImGui::PushID(node.Id);
  2016. ImGui::BeginGroup();
  2017. }
  2018. void EndNode()
  2019. {
  2020. assert(GImNodes->CurrentScope == ImNodesScope_Node);
  2021. GImNodes->CurrentScope = ImNodesScope_Editor;
  2022. ImNodesEditorContext& editor = EditorContextGet();
  2023. // The node's rectangle depends on the ImGui UI group size.
  2024. ImGui::EndGroup();
  2025. ImGui::PopID();
  2026. ImNodeData& node = editor.Nodes.Pool[GImNodes->CurrentNodeIdx];
  2027. node.Rect = GetItemRect();
  2028. node.Rect.Expand(node.LayoutStyle.Padding);
  2029. if (node.Rect.Contains(GImNodes->MousePos))
  2030. {
  2031. GImNodes->NodeIndicesOverlappingWithMouse.push_back(GImNodes->CurrentNodeIdx);
  2032. }
  2033. }
  2034. ImVec2 GetNodeDimensions(int node_id)
  2035. {
  2036. ImNodesEditorContext& editor = EditorContextGet();
  2037. const int node_idx = ObjectPoolFind(editor.Nodes, node_id);
  2038. assert(node_idx != -1); // invalid node_id
  2039. const ImNodeData& node = editor.Nodes.Pool[node_idx];
  2040. return node.Rect.GetSize();
  2041. }
  2042. void BeginNodeTitleBar()
  2043. {
  2044. assert(GImNodes->CurrentScope == ImNodesScope_Node);
  2045. ImGui::BeginGroup();
  2046. }
  2047. void EndNodeTitleBar()
  2048. {
  2049. assert(GImNodes->CurrentScope == ImNodesScope_Node);
  2050. ImGui::EndGroup();
  2051. ImNodesEditorContext& editor = EditorContextGet();
  2052. ImNodeData& node = editor.Nodes.Pool[GImNodes->CurrentNodeIdx];
  2053. node.TitleBarContentRect = GetItemRect();
  2054. ImGui::ItemAdd(GetNodeTitleRect(node), ImGui::GetID("title_bar"));
  2055. ImGui::SetCursorPos(GridSpaceToEditorSpace(editor, GetNodeContentOrigin(node)));
  2056. }
  2057. void BeginInputAttribute(const int id, const ImNodesPinShape shape)
  2058. {
  2059. BeginPinAttribute(id, ImNodesAttributeType_Input, shape, GImNodes->CurrentNodeIdx);
  2060. }
  2061. void EndInputAttribute() { EndPinAttribute(); }
  2062. void BeginOutputAttribute(const int id, const ImNodesPinShape shape)
  2063. {
  2064. BeginPinAttribute(id, ImNodesAttributeType_Output, shape, GImNodes->CurrentNodeIdx);
  2065. }
  2066. void EndOutputAttribute() { EndPinAttribute(); }
  2067. void BeginStaticAttribute(const int id)
  2068. {
  2069. // Make sure to call BeginNode() before calling BeginAttribute()
  2070. assert(GImNodes->CurrentScope == ImNodesScope_Node);
  2071. GImNodes->CurrentScope = ImNodesScope_Attribute;
  2072. GImNodes->CurrentAttributeId = id;
  2073. ImGui::BeginGroup();
  2074. ImGui::PushID(id);
  2075. }
  2076. void EndStaticAttribute()
  2077. {
  2078. // Make sure to call BeginNode() before calling BeginAttribute()
  2079. assert(GImNodes->CurrentScope == ImNodesScope_Attribute);
  2080. GImNodes->CurrentScope = ImNodesScope_Node;
  2081. ImGui::PopID();
  2082. ImGui::EndGroup();
  2083. if (ImGui::IsItemActive())
  2084. {
  2085. GImNodes->ActiveAttribute = true;
  2086. GImNodes->ActiveAttributeId = GImNodes->CurrentAttributeId;
  2087. }
  2088. }
  2089. void PushAttributeFlag(const ImNodesAttributeFlags flag)
  2090. {
  2091. GImNodes->CurrentAttributeFlags |= flag;
  2092. GImNodes->AttributeFlagStack.push_back(GImNodes->CurrentAttributeFlags);
  2093. }
  2094. void PopAttributeFlag()
  2095. {
  2096. // PopAttributeFlag called without a matching PushAttributeFlag!
  2097. // The bottom value is always the default value, pushed in Initialize().
  2098. assert(GImNodes->AttributeFlagStack.size() > 1);
  2099. GImNodes->AttributeFlagStack.pop_back();
  2100. GImNodes->CurrentAttributeFlags = GImNodes->AttributeFlagStack.back();
  2101. }
  2102. void Link(const int id, const int start_attr_id, const int end_attr_id)
  2103. {
  2104. assert(GImNodes->CurrentScope == ImNodesScope_Editor);
  2105. ImNodesEditorContext& editor = EditorContextGet();
  2106. ImLinkData& link = ObjectPoolFindOrCreateObject(editor.Links, id);
  2107. link.Id = id;
  2108. link.StartPinIdx = ObjectPoolFindOrCreateIndex(editor.Pins, start_attr_id);
  2109. link.EndPinIdx = ObjectPoolFindOrCreateIndex(editor.Pins, end_attr_id);
  2110. link.ColorStyle.Base = GImNodes->Style.Colors[ImNodesCol_Link];
  2111. link.ColorStyle.Hovered = GImNodes->Style.Colors[ImNodesCol_LinkHovered];
  2112. link.ColorStyle.Selected = GImNodes->Style.Colors[ImNodesCol_LinkSelected];
  2113. // Check if this link was created by the current link event
  2114. if ((editor.ClickInteraction.Type == ImNodesClickInteractionType_LinkCreation &&
  2115. editor.Pins.Pool[link.EndPinIdx].Flags & ImNodesAttributeFlags_EnableLinkCreationOnSnap &&
  2116. editor.ClickInteraction.LinkCreation.StartPinIdx == link.StartPinIdx &&
  2117. editor.ClickInteraction.LinkCreation.EndPinIdx == link.EndPinIdx) ||
  2118. (editor.ClickInteraction.LinkCreation.StartPinIdx == link.EndPinIdx &&
  2119. editor.ClickInteraction.LinkCreation.EndPinIdx == link.StartPinIdx))
  2120. {
  2121. GImNodes->SnapLinkIdx = ObjectPoolFindOrCreateIndex(editor.Links, id);
  2122. }
  2123. }
  2124. void PushColorStyle(const ImNodesCol item, unsigned int color)
  2125. {
  2126. GImNodes->ColorModifierStack.push_back(ImNodesColElement(GImNodes->Style.Colors[item], item));
  2127. GImNodes->Style.Colors[item] = color;
  2128. }
  2129. void PopColorStyle()
  2130. {
  2131. assert(GImNodes->ColorModifierStack.size() > 0);
  2132. const ImNodesColElement elem = GImNodes->ColorModifierStack.back();
  2133. GImNodes->Style.Colors[elem.Item] = elem.Color;
  2134. GImNodes->ColorModifierStack.pop_back();
  2135. }
  2136. float& LookupStyleVar(const ImNodesStyleVar item)
  2137. {
  2138. // TODO: once the switch gets too big and unwieldy to work with, we could do
  2139. // a byte-offset lookup into the Style struct, using the StyleVar as an
  2140. // index. This is how ImGui does it.
  2141. float* style_var = 0;
  2142. switch (item)
  2143. {
  2144. case ImNodesStyleVar_GridSpacing:
  2145. style_var = &GImNodes->Style.GridSpacing;
  2146. break;
  2147. case ImNodesStyleVar_NodeCornerRounding:
  2148. style_var = &GImNodes->Style.NodeCornerRounding;
  2149. break;
  2150. case ImNodesStyleVar_NodePaddingHorizontal:
  2151. style_var = &GImNodes->Style.NodePaddingHorizontal;
  2152. break;
  2153. case ImNodesStyleVar_NodePaddingVertical:
  2154. style_var = &GImNodes->Style.NodePaddingVertical;
  2155. break;
  2156. case ImNodesStyleVar_NodeBorderThickness:
  2157. style_var = &GImNodes->Style.NodeBorderThickness;
  2158. break;
  2159. case ImNodesStyleVar_LinkThickness:
  2160. style_var = &GImNodes->Style.LinkThickness;
  2161. break;
  2162. case ImNodesStyleVar_LinkLineSegmentsPerLength:
  2163. style_var = &GImNodes->Style.LinkLineSegmentsPerLength;
  2164. break;
  2165. case ImNodesStyleVar_LinkHoverDistance:
  2166. style_var = &GImNodes->Style.LinkHoverDistance;
  2167. break;
  2168. case ImNodesStyleVar_PinCircleRadius:
  2169. style_var = &GImNodes->Style.PinCircleRadius;
  2170. break;
  2171. case ImNodesStyleVar_PinQuadSideLength:
  2172. style_var = &GImNodes->Style.PinQuadSideLength;
  2173. break;
  2174. case ImNodesStyleVar_PinTriangleSideLength:
  2175. style_var = &GImNodes->Style.PinTriangleSideLength;
  2176. break;
  2177. case ImNodesStyleVar_PinLineThickness:
  2178. style_var = &GImNodes->Style.PinLineThickness;
  2179. break;
  2180. case ImNodesStyleVar_PinHoverRadius:
  2181. style_var = &GImNodes->Style.PinHoverRadius;
  2182. break;
  2183. case ImNodesStyleVar_PinOffset:
  2184. style_var = &GImNodes->Style.PinOffset;
  2185. break;
  2186. default:
  2187. assert(!"Invalid StyleVar value!");
  2188. }
  2189. return *style_var;
  2190. }
  2191. void PushStyleVar(const ImNodesStyleVar item, const float value)
  2192. {
  2193. float& style_var = LookupStyleVar(item);
  2194. GImNodes->StyleModifierStack.push_back(ImNodesStyleVarElement(style_var, item));
  2195. style_var = value;
  2196. }
  2197. void PopStyleVar()
  2198. {
  2199. assert(GImNodes->StyleModifierStack.size() > 0);
  2200. const ImNodesStyleVarElement style_elem = GImNodes->StyleModifierStack.back();
  2201. GImNodes->StyleModifierStack.pop_back();
  2202. float& style_var = LookupStyleVar(style_elem.Item);
  2203. style_var = style_elem.Value;
  2204. }
  2205. void SetNodeScreenSpacePos(const int node_id, const ImVec2& screen_space_pos)
  2206. {
  2207. ImNodesEditorContext& editor = EditorContextGet();
  2208. ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id);
  2209. node.Origin = ScreenSpaceToGridSpace(editor, screen_space_pos);
  2210. }
  2211. void SetNodeEditorSpacePos(const int node_id, const ImVec2& editor_space_pos)
  2212. {
  2213. ImNodesEditorContext& editor = EditorContextGet();
  2214. ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id);
  2215. node.Origin = EditorSpaceToGridSpace(editor, editor_space_pos);
  2216. }
  2217. void SetNodeGridSpacePos(const int node_id, const ImVec2& grid_pos)
  2218. {
  2219. ImNodesEditorContext& editor = EditorContextGet();
  2220. ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id);
  2221. node.Origin = grid_pos;
  2222. }
  2223. void SetNodeDraggable(const int node_id, const bool draggable)
  2224. {
  2225. ImNodesEditorContext& editor = EditorContextGet();
  2226. ImNodeData& node = ObjectPoolFindOrCreateObject(editor.Nodes, node_id);
  2227. node.Draggable = draggable;
  2228. }
  2229. ImVec2 GetNodeScreenSpacePos(const int node_id)
  2230. {
  2231. ImNodesEditorContext& editor = EditorContextGet();
  2232. const int node_idx = ObjectPoolFind(editor.Nodes, node_id);
  2233. assert(node_idx != -1);
  2234. ImNodeData& node = editor.Nodes.Pool[node_idx];
  2235. return GridSpaceToScreenSpace(editor, node.Origin);
  2236. }
  2237. ImVec2 GetNodeEditorSpacePos(const int node_id)
  2238. {
  2239. ImNodesEditorContext& editor = EditorContextGet();
  2240. const int node_idx = ObjectPoolFind(editor.Nodes, node_id);
  2241. assert(node_idx != -1);
  2242. ImNodeData& node = editor.Nodes.Pool[node_idx];
  2243. return GridSpaceToEditorSpace(editor, node.Origin);
  2244. }
  2245. ImVec2 GetNodeGridSpacePos(const int node_id)
  2246. {
  2247. ImNodesEditorContext& editor = EditorContextGet();
  2248. const int node_idx = ObjectPoolFind(editor.Nodes, node_id);
  2249. assert(node_idx != -1);
  2250. ImNodeData& node = editor.Nodes.Pool[node_idx];
  2251. return node.Origin;
  2252. }
  2253. bool IsEditorHovered() { return MouseInCanvas(); }
  2254. bool IsNodeHovered(int* const node_id)
  2255. {
  2256. assert(GImNodes->CurrentScope == ImNodesScope_None);
  2257. assert(node_id != NULL);
  2258. const bool is_hovered = GImNodes->HoveredNodeIdx.HasValue();
  2259. if (is_hovered)
  2260. {
  2261. const ImNodesEditorContext& editor = EditorContextGet();
  2262. *node_id = editor.Nodes.Pool[GImNodes->HoveredNodeIdx.Value()].Id;
  2263. }
  2264. return is_hovered;
  2265. }
  2266. bool IsLinkHovered(int* const link_id)
  2267. {
  2268. assert(GImNodes->CurrentScope == ImNodesScope_None);
  2269. assert(link_id != NULL);
  2270. const bool is_hovered = GImNodes->HoveredLinkIdx.HasValue();
  2271. if (is_hovered)
  2272. {
  2273. const ImNodesEditorContext& editor = EditorContextGet();
  2274. *link_id = editor.Links.Pool[GImNodes->HoveredLinkIdx.Value()].Id;
  2275. }
  2276. return is_hovered;
  2277. }
  2278. bool IsPinHovered(int* const attr)
  2279. {
  2280. assert(GImNodes->CurrentScope == ImNodesScope_None);
  2281. assert(attr != NULL);
  2282. const bool is_hovered = GImNodes->HoveredPinIdx.HasValue();
  2283. if (is_hovered)
  2284. {
  2285. const ImNodesEditorContext& editor = EditorContextGet();
  2286. *attr = editor.Pins.Pool[GImNodes->HoveredPinIdx.Value()].Id;
  2287. }
  2288. return is_hovered;
  2289. }
  2290. int NumSelectedNodes()
  2291. {
  2292. assert(GImNodes->CurrentScope == ImNodesScope_None);
  2293. const ImNodesEditorContext& editor = EditorContextGet();
  2294. return editor.SelectedNodeIndices.size();
  2295. }
  2296. int NumSelectedLinks()
  2297. {
  2298. assert(GImNodes->CurrentScope == ImNodesScope_None);
  2299. const ImNodesEditorContext& editor = EditorContextGet();
  2300. return editor.SelectedLinkIndices.size();
  2301. }
  2302. void GetSelectedNodes(int* node_ids)
  2303. {
  2304. assert(node_ids != NULL);
  2305. const ImNodesEditorContext& editor = EditorContextGet();
  2306. for (int i = 0; i < editor.SelectedNodeIndices.size(); ++i)
  2307. {
  2308. const int node_idx = editor.SelectedNodeIndices[i];
  2309. node_ids[i] = editor.Nodes.Pool[node_idx].Id;
  2310. }
  2311. }
  2312. void GetSelectedLinks(int* link_ids)
  2313. {
  2314. assert(link_ids != NULL);
  2315. const ImNodesEditorContext& editor = EditorContextGet();
  2316. for (int i = 0; i < editor.SelectedLinkIndices.size(); ++i)
  2317. {
  2318. const int link_idx = editor.SelectedLinkIndices[i];
  2319. link_ids[i] = editor.Links.Pool[link_idx].Id;
  2320. }
  2321. }
  2322. void ClearNodeSelection()
  2323. {
  2324. ImNodesEditorContext& editor = EditorContextGet();
  2325. editor.SelectedNodeIndices.clear();
  2326. }
  2327. void ClearNodeSelection(int node_id)
  2328. {
  2329. ImNodesEditorContext& editor = EditorContextGet();
  2330. ClearObjectSelection(editor.Nodes, editor.SelectedNodeIndices, node_id);
  2331. }
  2332. void ClearLinkSelection()
  2333. {
  2334. ImNodesEditorContext& editor = EditorContextGet();
  2335. editor.SelectedLinkIndices.clear();
  2336. }
  2337. void ClearLinkSelection(int link_id)
  2338. {
  2339. ImNodesEditorContext& editor = EditorContextGet();
  2340. ClearObjectSelection(editor.Links, editor.SelectedLinkIndices, link_id);
  2341. }
  2342. void SelectNode(int node_id)
  2343. {
  2344. ImNodesEditorContext& editor = EditorContextGet();
  2345. SelectObject(editor.Nodes, editor.SelectedNodeIndices, node_id);
  2346. }
  2347. void SelectLink(int link_id)
  2348. {
  2349. ImNodesEditorContext& editor = EditorContextGet();
  2350. SelectObject(editor.Links, editor.SelectedLinkIndices, link_id);
  2351. }
  2352. bool IsNodeSelected(int node_id)
  2353. {
  2354. ImNodesEditorContext& editor = EditorContextGet();
  2355. return IsObjectSelected(editor.Nodes, editor.SelectedNodeIndices, node_id);
  2356. }
  2357. bool IsLinkSelected(int link_id)
  2358. {
  2359. ImNodesEditorContext& editor = EditorContextGet();
  2360. return IsObjectSelected(editor.Links, editor.SelectedLinkIndices, link_id);
  2361. }
  2362. bool IsAttributeActive()
  2363. {
  2364. assert((GImNodes->CurrentScope & ImNodesScope_Node) != 0);
  2365. if (!GImNodes->ActiveAttribute)
  2366. {
  2367. return false;
  2368. }
  2369. return GImNodes->ActiveAttributeId == GImNodes->CurrentAttributeId;
  2370. }
  2371. bool IsAnyAttributeActive(int* const attribute_id)
  2372. {
  2373. assert((GImNodes->CurrentScope & (ImNodesScope_Node | ImNodesScope_Attribute)) == 0);
  2374. if (!GImNodes->ActiveAttribute)
  2375. {
  2376. return false;
  2377. }
  2378. if (attribute_id != NULL)
  2379. {
  2380. *attribute_id = GImNodes->ActiveAttributeId;
  2381. }
  2382. return true;
  2383. }
  2384. bool IsLinkStarted(int* const started_at_id)
  2385. {
  2386. // Call this function after EndNodeEditor()!
  2387. assert(GImNodes->CurrentScope == ImNodesScope_None);
  2388. assert(started_at_id != NULL);
  2389. const bool is_started = (GImNodes->ImNodesUIState & ImNodesUIState_LinkStarted) != 0;
  2390. if (is_started)
  2391. {
  2392. const ImNodesEditorContext& editor = EditorContextGet();
  2393. const int pin_idx = editor.ClickInteraction.LinkCreation.StartPinIdx;
  2394. *started_at_id = editor.Pins.Pool[pin_idx].Id;
  2395. }
  2396. return is_started;
  2397. }
  2398. bool IsLinkDropped(int* const started_at_id, const bool including_detached_links)
  2399. {
  2400. // Call this function after EndNodeEditor()!
  2401. assert(GImNodes->CurrentScope == ImNodesScope_None);
  2402. const ImNodesEditorContext& editor = EditorContextGet();
  2403. const bool link_dropped =
  2404. (GImNodes->ImNodesUIState & ImNodesUIState_LinkDropped) != 0 &&
  2405. (including_detached_links ||
  2406. editor.ClickInteraction.LinkCreation.Type != ImNodesLinkCreationType_FromDetach);
  2407. if (link_dropped && started_at_id)
  2408. {
  2409. const int pin_idx = editor.ClickInteraction.LinkCreation.StartPinIdx;
  2410. *started_at_id = editor.Pins.Pool[pin_idx].Id;
  2411. }
  2412. return link_dropped;
  2413. }
  2414. bool IsLinkCreated(
  2415. int* const started_at_pin_id,
  2416. int* const ended_at_pin_id,
  2417. bool* const created_from_snap)
  2418. {
  2419. assert(GImNodes->CurrentScope == ImNodesScope_None);
  2420. assert(started_at_pin_id != NULL);
  2421. assert(ended_at_pin_id != NULL);
  2422. const bool is_created = (GImNodes->ImNodesUIState & ImNodesUIState_LinkCreated) != 0;
  2423. if (is_created)
  2424. {
  2425. const ImNodesEditorContext& editor = EditorContextGet();
  2426. const int start_idx = editor.ClickInteraction.LinkCreation.StartPinIdx;
  2427. const int end_idx = editor.ClickInteraction.LinkCreation.EndPinIdx.Value();
  2428. const ImPinData& start_pin = editor.Pins.Pool[start_idx];
  2429. const ImPinData& end_pin = editor.Pins.Pool[end_idx];
  2430. if (start_pin.Type == ImNodesAttributeType_Output)
  2431. {
  2432. *started_at_pin_id = start_pin.Id;
  2433. *ended_at_pin_id = end_pin.Id;
  2434. }
  2435. else
  2436. {
  2437. *started_at_pin_id = end_pin.Id;
  2438. *ended_at_pin_id = start_pin.Id;
  2439. }
  2440. if (created_from_snap)
  2441. {
  2442. *created_from_snap =
  2443. editor.ClickInteraction.Type == ImNodesClickInteractionType_LinkCreation;
  2444. }
  2445. }
  2446. return is_created;
  2447. }
  2448. bool IsLinkCreated(
  2449. int* started_at_node_id,
  2450. int* started_at_pin_id,
  2451. int* ended_at_node_id,
  2452. int* ended_at_pin_id,
  2453. bool* created_from_snap)
  2454. {
  2455. assert(GImNodes->CurrentScope == ImNodesScope_None);
  2456. assert(started_at_node_id != NULL);
  2457. assert(started_at_pin_id != NULL);
  2458. assert(ended_at_node_id != NULL);
  2459. assert(ended_at_pin_id != NULL);
  2460. const bool is_created = (GImNodes->ImNodesUIState & ImNodesUIState_LinkCreated) != 0;
  2461. if (is_created)
  2462. {
  2463. const ImNodesEditorContext& editor = EditorContextGet();
  2464. const int start_idx = editor.ClickInteraction.LinkCreation.StartPinIdx;
  2465. const int end_idx = editor.ClickInteraction.LinkCreation.EndPinIdx.Value();
  2466. const ImPinData& start_pin = editor.Pins.Pool[start_idx];
  2467. const ImNodeData& start_node = editor.Nodes.Pool[start_pin.ParentNodeIdx];
  2468. const ImPinData& end_pin = editor.Pins.Pool[end_idx];
  2469. const ImNodeData& end_node = editor.Nodes.Pool[end_pin.ParentNodeIdx];
  2470. if (start_pin.Type == ImNodesAttributeType_Output)
  2471. {
  2472. *started_at_pin_id = start_pin.Id;
  2473. *started_at_node_id = start_node.Id;
  2474. *ended_at_pin_id = end_pin.Id;
  2475. *ended_at_node_id = end_node.Id;
  2476. }
  2477. else
  2478. {
  2479. *started_at_pin_id = end_pin.Id;
  2480. *started_at_node_id = end_node.Id;
  2481. *ended_at_pin_id = start_pin.Id;
  2482. *ended_at_node_id = start_node.Id;
  2483. }
  2484. if (created_from_snap)
  2485. {
  2486. *created_from_snap =
  2487. editor.ClickInteraction.Type == ImNodesClickInteractionType_LinkCreation;
  2488. }
  2489. }
  2490. return is_created;
  2491. }
  2492. bool IsLinkDestroyed(int* const link_id)
  2493. {
  2494. assert(GImNodes->CurrentScope == ImNodesScope_None);
  2495. const bool link_destroyed = GImNodes->DeletedLinkIdx.HasValue();
  2496. if (link_destroyed)
  2497. {
  2498. const ImNodesEditorContext& editor = EditorContextGet();
  2499. const int link_idx = GImNodes->DeletedLinkIdx.Value();
  2500. *link_id = editor.Links.Pool[link_idx].Id;
  2501. }
  2502. return link_destroyed;
  2503. }
  2504. namespace
  2505. {
  2506. void NodeLineHandler(ImNodesEditorContext& editor, const char* const line)
  2507. {
  2508. int id;
  2509. int x, y;
  2510. if (sscanf(line, "[node.%i", &id) == 1)
  2511. {
  2512. const int node_idx = ObjectPoolFindOrCreateIndex(editor.Nodes, id);
  2513. GImNodes->CurrentNodeIdx = node_idx;
  2514. ImNodeData& node = editor.Nodes.Pool[node_idx];
  2515. node.Id = id;
  2516. }
  2517. else if (sscanf(line, "origin=%i,%i", &x, &y) == 2)
  2518. {
  2519. ImNodeData& node = editor.Nodes.Pool[GImNodes->CurrentNodeIdx];
  2520. node.Origin = ImVec2((float)x, (float)y);
  2521. }
  2522. }
  2523. void EditorLineHandler(ImNodesEditorContext& editor, const char* const line)
  2524. {
  2525. (void)sscanf(line, "panning=%f,%f", &editor.Panning.x, &editor.Panning.y);
  2526. }
  2527. } // namespace
  2528. const char* SaveCurrentEditorStateToIniString(size_t* const data_size)
  2529. {
  2530. return SaveEditorStateToIniString(&EditorContextGet(), data_size);
  2531. }
  2532. const char* SaveEditorStateToIniString(
  2533. const ImNodesEditorContext* const editor_ptr,
  2534. size_t* const data_size)
  2535. {
  2536. assert(editor_ptr != NULL);
  2537. const ImNodesEditorContext& editor = *editor_ptr;
  2538. GImNodes->TextBuffer.clear();
  2539. // TODO: check to make sure that the estimate is the upper bound of element
  2540. GImNodes->TextBuffer.reserve(64 * editor.Nodes.Pool.size());
  2541. GImNodes->TextBuffer.appendf(
  2542. "[editor]\npanning=%i,%i\n", (int)editor.Panning.x, (int)editor.Panning.y);
  2543. for (int i = 0; i < editor.Nodes.Pool.size(); i++)
  2544. {
  2545. if (editor.Nodes.InUse[i])
  2546. {
  2547. const ImNodeData& node = editor.Nodes.Pool[i];
  2548. GImNodes->TextBuffer.appendf("\n[node.%d]\n", node.Id);
  2549. GImNodes->TextBuffer.appendf("origin=%i,%i\n", (int)node.Origin.x, (int)node.Origin.y);
  2550. }
  2551. }
  2552. if (data_size != NULL)
  2553. {
  2554. *data_size = GImNodes->TextBuffer.size();
  2555. }
  2556. return GImNodes->TextBuffer.c_str();
  2557. }
  2558. void LoadCurrentEditorStateFromIniString(const char* const data, const size_t data_size)
  2559. {
  2560. LoadEditorStateFromIniString(&EditorContextGet(), data, data_size);
  2561. }
  2562. void LoadEditorStateFromIniString(
  2563. ImNodesEditorContext* const editor_ptr,
  2564. const char* const data,
  2565. const size_t data_size)
  2566. {
  2567. if (data_size == 0u)
  2568. {
  2569. return;
  2570. }
  2571. ImNodesEditorContext& editor = editor_ptr == NULL ? EditorContextGet() : *editor_ptr;
  2572. char* buf = (char*)ImGui::MemAlloc(data_size + 1);
  2573. const char* buf_end = buf + data_size;
  2574. memcpy(buf, data, data_size);
  2575. buf[data_size] = 0;
  2576. void (*line_handler)(ImNodesEditorContext&, const char*);
  2577. line_handler = NULL;
  2578. char* line_end = NULL;
  2579. for (char* line = buf; line < buf_end; line = line_end + 1)
  2580. {
  2581. while (*line == '\n' || *line == '\r')
  2582. {
  2583. line++;
  2584. }
  2585. line_end = line;
  2586. while (line_end < buf_end && *line_end != '\n' && *line_end != '\r')
  2587. {
  2588. line_end++;
  2589. }
  2590. line_end[0] = 0;
  2591. if (*line == ';' || *line == '\0')
  2592. {
  2593. continue;
  2594. }
  2595. if (line[0] == '[' && line_end[-1] == ']')
  2596. {
  2597. line_end[-1] = 0;
  2598. if (strncmp(line + 1, "node", 4) == 0)
  2599. {
  2600. line_handler = NodeLineHandler;
  2601. }
  2602. else if (strcmp(line + 1, "editor") == 0)
  2603. {
  2604. line_handler = EditorLineHandler;
  2605. }
  2606. }
  2607. if (line_handler != NULL)
  2608. {
  2609. line_handler(editor, line);
  2610. }
  2611. }
  2612. ImGui::MemFree(buf);
  2613. }
  2614. void SaveCurrentEditorStateToIniFile(const char* const file_name)
  2615. {
  2616. SaveEditorStateToIniFile(&EditorContextGet(), file_name);
  2617. }
  2618. void SaveEditorStateToIniFile(const ImNodesEditorContext* const editor, const char* const file_name)
  2619. {
  2620. size_t data_size = 0u;
  2621. const char* data = SaveEditorStateToIniString(editor, &data_size);
  2622. FILE* file = ImFileOpen(file_name, "wt");
  2623. if (!file)
  2624. {
  2625. return;
  2626. }
  2627. fwrite(data, sizeof(char), data_size, file);
  2628. fclose(file);
  2629. }
  2630. void LoadCurrentEditorStateFromIniFile(const char* const file_name)
  2631. {
  2632. LoadEditorStateFromIniFile(&EditorContextGet(), file_name);
  2633. }
  2634. void LoadEditorStateFromIniFile(ImNodesEditorContext* const editor, const char* const file_name)
  2635. {
  2636. size_t data_size = 0u;
  2637. char* file_data = (char*)ImFileLoadToMemory(file_name, "rb", &data_size);
  2638. if (!file_data)
  2639. {
  2640. return;
  2641. }
  2642. LoadEditorStateFromIniString(editor, file_data, data_size);
  2643. ImGui::MemFree(file_data);
  2644. }
  2645. } // namespace ImNodes