commit ed62b384e570ac6fe6b22dd53a7909852f250f4c Author: Medusa Slockbower Date: Tue Jun 3 14:44:01 2025 -0400 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..303aeeb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/docs/ +Doxyfile diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..f39725f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.5) + +set(VERSION_MAJOR 0) +set(VERSION_MINOR 0) +set(VERSION_PATCH 1) +set(PROJECT_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") + +project(imnode-graph VERSION ${PROJECT_VERSION}) + +find_package(RapidJSON REQUIRED) + +set(IMNODE_GRAPH_SOURCES + imnode_graph.cpp +) + +set(IMNODE_GRAPH_HEADERS + imnode_graph.h + imnode_graph_internal.h + imnode_graph_math.h +) + +add_library(imnode-graph STATIC + ${IMNODE_GRAPH_SOURCES} + ${IMNODE_GRAPH_HEADERS} +) + +# DOXYGEN ============================================================================================================== +# https://vicrucann.github.io/tutorials/quick-cmake-doxygen/ + +find_package(Doxygen) + +if(DOXYGEN_FOUND) + get_filename_component(DOXYGEN_PROJECT_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) + set(DOXYGEN_CONFIG_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) + set(DOXYGEN_CONFIG_OUT ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile) + + configure_file(${DOXYGEN_CONFIG_IN} ${DOXYGEN_CONFIG_OUT} @ONLY) + message("Doxygen Build Started.") + + if(WIN32) + add_custom_target(imnode-graph-documentation ALL + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_CONFIG_OUT} + COMMAND start firefox "${CMAKE_CURRENT_SOURCE_DIR}/Documentation/html/index.html" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating Doxygen Documentation" + VERBATIM) + else() + add_custom_target(imnode-graph-documentation ALL + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_CONFIG_OUT} + COMMAND firefox "${CMAKE_CURRENT_SOURCE_DIR}/Documentation/html/index.html" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating Doxygen Documentation" + VERBATIM) + endif() +else() + message("Doxygen not found.") +endif() \ No newline at end of file diff --git a/Doxyfile.in b/Doxyfile.in new file mode 100755 index 0000000..324307d --- /dev/null +++ b/Doxyfile.in @@ -0,0 +1,5 @@ +OUTPUT_DIRECTORY = "@CMAKE_CURRENT_SOURCE_DIR@/Documentation/" +INPUT = "@CMAKE_CURRENT_SOURCE_DIR@/README.md" "@CMAKE_CURRENT_SOURCE_DIR@/" +RECURSIVE = YES +PROJECT_NAME = "@DOXYGEN_PROJECT_NAME@" +PROJECT_NUMBER = "@CMAKE_PROJECT_VERSION@" \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..41d1ce9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + imnode-graph, and open source extension for Dear ImGui that adds functionality for drawing a node graph. + Copyright (C) 2024 Medusa Slockbower + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + imnode-graph Copyright (C) 2024 Medusa Slockbower + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100755 index 0000000..f859353 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# imgui-nodes +Node Graph Editor for Dear ImGui diff --git a/imnode_graph.cpp b/imnode_graph.cpp new file mode 100755 index 0000000..a032d8c --- /dev/null +++ b/imnode_graph.cpp @@ -0,0 +1,2179 @@ +// ===================================================================================================================== +// imnode-graph, and open source extension for Dear ImGui that adds functionality for drawing a node graph. +// Copyright (C) 2024 Medusa Slockbower +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ===================================================================================================================== + +#include "imnode_graph.h" +#include "imnode_graph_internal.h" + +#include + +#include + +//#define IMNODE_GRAPH_DEBUG_PIN_BOUNDS + +struct ImNodeFontConfig +{ + char* Path; + float Size; + const ImWchar* GlyphRanges; +}; + +ImNodeGraphContext* GImNodeGraph = nullptr; // Global Node Graph Context +ImVector GFonts; // Fonts added to ImNodeGraph +float GFontUpscale = 4.0f; + +ImVec4 operator*(const ImVec4& v, float s) { return { v.x * s, v.y * s, v.z * s, v.w * s }; } + +// ===================================================================================================================== +// Internal Extensions +// ===================================================================================================================== + +// Helper to check if any key mods are active +bool ImGui::IsAnyModKeyDown() +{ + ImGuiContext& G = *GImGui; + ImGuiIO& IO = G.IO; + + return IO.KeyMods != ImGuiMod_None; +} + +// ===================================================================================================================== +// Internal Functionality +// ===================================================================================================================== + + +// Math ---------------------------------------------------------------------------------------------------------------- + +// AABB Collision +bool ImAABB(const ImRect &a, const ImRect &b) +{ + return a.Max.x > b.Min.x + && a.Min.x < b.Max.x + && a.Max.y > b.Min.y + && a.Min.y < b.Max.y; +} + + +// Context ------------------------------------------------------------------------------------------------------------- + +ImNodeGraphContext::ImNodeGraphContext() + : Initialized(false) + , Scope(ImNodeGraphScope_None) + , CurrentGraph(nullptr) +{ + +} + +void ImNodeGraph::Initialize() +{ + ImGuiContext& Ctx = *ImGui::GetCurrentContext(); + ImNodeGraphContext& G = *GImNodeGraph; + IM_ASSERT(!G.Initialized); + + // If no fonts were set up, add the default font + if(G.Fonts.empty()) + { + if(Ctx.IO.Fonts->Fonts.Size == 0) Ctx.IO.Fonts->AddFontDefault(); + + LoadFonts(); + } + + G.Initialized = true; +} + +void ImNodeGraph::Shutdown() +{ + ImNodeGraphContext& G = *GImNodeGraph; + IM_ASSERT(G.Initialized); + + G.Graphs.clear_delete(); + GFonts.clear(); +} + +void ImNodeGraph::LoadFonts() +{ + if(GFonts.empty()) + { + LoadDefaultFont(); + return; + } + + ImGuiContext& Ctx = *ImGui::GetCurrentContext(); + ImNodeGraphContext& G = *GImNodeGraph; + + bool first = true; + for(const auto& font : GFonts) + { + ImFontConfig cfg = ImFontConfig(); + cfg.OversampleH = cfg.OversampleV = 1; + cfg.SizePixels = font.Size * GFontUpscale; + cfg.MergeMode = !first; + cfg.PixelSnapH = false; + G.Fonts.push_back(Ctx.IO.Fonts->AddFontFromFileTTF(font.Path, 0, &cfg, font.GlyphRanges)); + + first = false; + } +} + +void ImNodeGraph::LoadDefaultFont() +{ + ImGuiContext& Ctx = *ImGui::GetCurrentContext(); + ImNodeGraphContext& G = *GImNodeGraph; + + ImFontConfig cfg = ImFontConfig(); + cfg.OversampleH = cfg.OversampleV = 1; + cfg.SizePixels = 20.0f * GFontUpscale; + cfg.MergeMode = false; + cfg.PixelSnapH = false; + G.Fonts.push_back(Ctx.IO.Fonts->AddFontDefault(&cfg)); +} + + +// Graph --------------------------------------------------------------------------------------------------------------- + +ImNodeGraphData::ImNodeGraphData(ImNodeGraphContext* ctx, const char* name) + : Ctx(ctx) + , Flags(ImNodeGraphFlags_None) + , Name(nullptr) + , ID(ImHashStr(name)) + , TargetZoom(1.0f) + , IsPanning(false) + , CurrentNode(nullptr) + , CurrentPin(nullptr) + , SubmitCount(0) + , Validation(nullptr) + , Dragging(false) + , LockSelectRegion(false) +{ Name = ImStrdup(name); } + +void ImNodeGraphData::Reset() +{ + // Flags, Style, and Settings are not reset + + // Reset Camera + Camera = ImGraphCamera(); + TargetZoom = Camera.Scale; + IsPanning = false; + + // Reset Selection Vars + SelectRegionStart.Reset(); + SelectRegion.Clear(); + Selected.Clear(); + DragOffset = { 0, 0 }; + Dragging = false; + LockSelectRegion = false; + + // Reset Nodes + Nodes.Clear(); + HoveredNode.Reset(); + FocusedNode.Reset(); + HoveredPin.Reset(); + FocusedPin.Reset(); + + CurrentNode = nullptr; + CurrentPin = nullptr; + SubmitCount = 0; + + // Reset Connections + Connections.Clear(); + NewConnection.Reset(); + + // Validation Function is not Reset +} + +ImPinData* ImNodeGraphData::FindPin(ImPinPtr pin) +{ + ImNodeData& Node = Nodes[pin.Node]; + ImObjectPool& Pins = pin.Direction ? Node.OutputPins : Node.InputPins; + return Pins(pin.Pin) ? &Pins[pin.Pin] : nullptr; +} + +ImNodeData* ImNodeGraphData::FindNode(int id) +{ + for(int i = 0; i < Nodes.Size(); ++i) + { + if(Nodes[i].UserID == id) return &Nodes[i]; + } + return nullptr; +} + +ImPinData* ImNodeGraphData::FindPin(ImUserPinPtr pin) +{ + ImNodeData* Node = FindNode(pin.Node); + if(Node == nullptr) return nullptr; + ImObjectPool& Pins = pin.Direction ? Node->OutputPins : Node->InputPins; + for(int i = 0; i < Pins.Size(); ++i) + { + if(Pins[i].UserID == pin.Pin) return &Pins[i]; + } + return nullptr; +} + +ImRect ImNodeGraphData::GetSelection() +{ + if(SelectRegionStart() == false) return { { -1, -1 }, { -1, -1 } }; + + ImVec2 mouse = ImGui::GetMousePos(); + return { ImMin(mouse, SelectRegionStart), ImMax(mouse, SelectRegionStart) }; +} + +void ImNodeGraphData::UpdateSelection(ImGuiID Node, bool allow_clear, bool removal) +{ + ImGuiContext& Ctx = *ImGui::GetCurrentContext(); + ImGuiIO& IO = Ctx.IO; + bool selected = Selected.Contains(Node); + + switch(IO.KeyMods) + { + case ImGuiMod_Ctrl: + if(selected) Selected.Erase(Node); + else Selected.Insert(Node); + break; + + default: + if(allow_clear) Selected.Clear(); + + case ImGuiMod_Shift: + if(removal) Selected.Erase(Node); + else Selected.Insert(Node); + } +} + +ImNodeGraphData* ImNodeGraph::FindGraphByID(ImGuiID id) +{ + ImNodeGraphContext& G = *GImNodeGraph; + return static_cast(G.GraphsById.GetVoidPtr(id)); +} + +ImNodeGraphData* ImNodeGraph::FindGraphByTitle(const char *title) +{ + ImGuiID id = ImHashStr(title); + return FindGraphByID(id); +} + +ImNodeGraphData* ImNodeGraph::CreateNewGraph(const char *title) +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = IM_NEW(ImNodeGraphData)(&G, title); + G.GraphsById.SetVoidPtr(Graph->ID, Graph); + + G.Graphs.push_back(Graph); + + return Graph; +} + +void ImNodeGraph::DrawGrid(const ImRect &grid_bounds) +{ + // Draw the grid + ImGuiWindow& DrawWindow = *ImGui::GetCurrentWindow(); + ImDrawList& DrawList = *DrawWindow.DrawList; + ImNodeGraphData& Graph = *GImNodeGraph->CurrentGraph; + ImNodeGraphStyle& Style = Graph.Style; + ImGraphCamera& Camera = Graph.Camera; + + const float GridSecondarySize = ImGui::GetFontSize() / Camera.Scale; + const float GridPrimarySize = GridSecondarySize * Style.GridPrimaryStep; + + const float GridSecondaryStep = GridSecondarySize * Camera.Scale; + const float GridPrimaryStep = GridPrimarySize * Camera.Scale; + + ImVec2 GridStart = ScreenToGrid(grid_bounds.Min); + GridStart = ImFloor(GridStart / GridPrimarySize) * GridPrimarySize; + GridStart = GridToScreen(GridStart); + + ImVec2 GridEnd = ScreenToGrid(grid_bounds.Max); + GridEnd = ImFloor(GridEnd / GridPrimarySize) * GridPrimarySize; + GridEnd = GridEnd + ImVec2{ GridPrimarySize, GridPrimarySize }; + GridEnd = GridToScreen(GridEnd); + + // Secondary Grid + for(float x = GridStart.x; x < GridEnd.x; x += GridSecondaryStep) + { + DrawList.AddLine( + { x, 0 }, { x, GridEnd.y } + , Style.Colors[ImNodeGraphColor_GridSecondaryLines], Style.GridSecondaryThickness * Camera.Scale + ); + } + + for(float y = GridStart.y; y < GridEnd.y; y += GridSecondaryStep) + { + DrawList.AddLine( + { 0, y }, { GridEnd.x, y } + , Style.Colors[ImNodeGraphColor_GridSecondaryLines], Style.GridSecondaryThickness * Camera.Scale + ); + } + + // Primary Grid + for(float x = GridStart.x; x < GridEnd.x; x += GridPrimaryStep) + { + DrawList.AddLine( + { x, 0 }, { x, GridEnd.y } + , Style.Colors[ImNodeGraphColor_GridPrimaryLines], Style.GridPrimaryThickness * Camera.Scale + ); + } + + for(float y = GridStart.y; y < GridEnd.y; y += GridPrimaryStep) + { + DrawList.AddLine( + { 0, y }, { GridEnd.x, y } + , Style.Colors[ImNodeGraphColor_GridPrimaryLines], Style.GridPrimaryThickness * Camera.Scale + ); + } +} + +void ImNodeGraph::GraphBehaviour(const ImRect& grid_bounds) +{ + // Context + ImGuiContext& Ctx = *ImGui::GetCurrentContext(); + ImGuiIO& IO = Ctx.IO; + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + ImObjectPool& Nodes = Graph.Nodes; + ImNodeGraphSettings& Settings = Graph.Settings; + ImGraphCamera& Camera = Graph.Camera; + + + // Check Focus + if(!ImGui::IsWindowFocused() || Graph.NewConnection()) + { + if(ImGui::IsMouseReleased(ImGuiMouseButton_Left) && Graph.NewConnection()) + { + Graph.NewConnection.Reset(); + ImGui::SetActiveID(0, ImGui::GetCurrentWindow()); + } + return; + } + + // Vars + const bool Hovered = ImGui::IsMouseHoveringRect(grid_bounds.Min, grid_bounds.Max); + + // Zooming + if(Hovered) Graph.TargetZoom += Ctx.IO.MouseWheel * Settings.ZoomRate * Camera.Scale; + Graph.TargetZoom = ImClamp(Graph.TargetZoom, Settings.ZoomBounds.x, Settings.ZoomBounds.y); + Camera.Scale = ImLerp(Camera.Scale, Graph.TargetZoom, Ctx.IO.DeltaTime * Settings.ZoomSmoothing); + + // Select Region + if(ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + if(not Hovered) return; + + if(Graph.FocusedNode() == false) + { + if(IO.KeyMods == ImGuiMod_None) Graph.Selected.Clear(); + } + else + { + ImVec2 mouse = ScreenToGrid(ImGui::GetMousePos()); + for(ImGuiID node : Graph.Selected) Nodes[node].DragOffset = mouse - Nodes[node].Root; + Nodes[Graph.FocusedNode].DragOffset = mouse - Nodes[Graph.FocusedNode].Root; + } + } + + // Item Focus + if(ImGui::IsAnyItemFocused()) return; + + // Pin Drag Connection & Node Focus + if(ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + if(Graph.FocusedNode() && !Graph.Dragging) + { + Graph.UpdateSelection(Graph.FocusedNode, true); + } + + Graph.FocusedNode.Reset(); + Graph.SelectRegionStart.Reset(); + Graph.SelectRegion.Clear(); + Graph.Dragging = false; + } + + // Dragging Nodes & Region Select + if(ImGui::IsMouseDragging(ImGuiMouseButton_Left)) + { + if(Graph.FocusedNode()) + { + if(!Graph.Selected.Contains(Graph.FocusedNode)) + { + Graph.UpdateSelection(Graph.FocusedNode, true); + } + + ImVec2 mouse = ScreenToGrid(ImGui::GetMousePos()); + for(ImGuiID node : Graph.Selected) + { + Nodes[node].Root = mouse - Nodes[node].DragOffset; + if(IO.KeyMods == ImGuiMod_Alt) Nodes[node].Root = SnapToGrid(Nodes[node].Root); + } + Graph.Dragging = true; + } + else if(Graph.SelectRegionStart() == false && !Graph.LockSelectRegion) + { + Graph.SelectRegionStart = ImGui::GetMousePos(); + } + } + + // Panning + if(Hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Middle)) + Graph.IsPanning = true; + + if(ImGui::IsMouseReleased(ImGuiMouseButton_Middle)) + Graph.IsPanning = false; + + if(Graph.IsPanning) + { + Camera.Position -= Ctx.IO.MouseDelta / Camera.Scale; + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); + } + + if(ImGui::IsKeyPressed(ImGuiKey_T)) + { + Nodes.PushToTop(Graph.Nodes.IdxToID[0]); + } +} + +void ImNodeGraph::DrawGraph(ImNodeGraphData* Graph) +{ + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + ImDrawListSplitter& Splitter = DrawList._Splitter; + ImObjectPool& Nodes = Graph->Nodes; + ImNodeGraphStyle& Style = Graph->Style; + ImGraphCamera& Camera = Graph->Camera; + + ImOptional prevFocus = Graph->FocusedNode; + Graph->HoveredNode.Reset(); + if(ImGui::IsWindowFocused() && !Graph->NewConnection()) + { + for(auto it = Nodes.rbegin(); it != Nodes.rend(); ++it) + { + if(NodeBehaviour(&*it)) break; + } + } + if(prevFocus != Graph->FocusedNode) + { + Graph->Nodes.PushToTop(Graph->FocusedNode); + } + + // Draw Nodes + for(ImNodeData& Node : Nodes) + { + SetChannel(Node.BgChannelIndex); + DrawNode(&Node); + } + + SortChannels(); + + Splitter.Merge(&DrawList); + Splitter.Clear(); + + if(Graph->NewConnection()) + { + ImPinData* pin = Graph->FindPin(Graph->NewConnection); + DrawConnection(pin, ImGui::GetMousePos()); + } + + for(int i = 0; i < Graph->Connections.Size(); ++i) + { + if(Graph->Connections(i) == false) continue; + + ImPinConnection& connection = Graph->Connections[i]; + + if(CheckConnectionValidity(i, connection)) continue; + + DrawConnection(Graph->FindPin(connection.A), Graph->FindPin(connection.B)); + } + + if(Graph->SelectRegionStart()) + { + ImRect Selection = Graph->GetSelection(); + + DrawList.AddRectFilled( + Selection.Min, Selection.Max + , Style.GetColorU32(ImNodeGraphColor_SelectRegionBackground) + , Style.SelectRegionRounding + ); + + DrawList.AddRect( + Selection.Min, Selection.Max + , Style.GetColorU32(ImNodeGraphColor_SelectRegionOutline) + , Style.SelectRegionRounding, 0 + , Style.SelectRegionOutlineThickness + ); + } +} + +ImVec2 ImNodeGraph::GridToWindow(const ImVec2 &pos) +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + + return GridToScreen(pos) - Graph.ScreenPos; +} + +ImVec2 ImNodeGraph::WindowToScreen(const ImVec2 &pos) +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + + return Graph.ScreenPos + pos; +} + +ImVec2 ImNodeGraph::GridToScreen(const ImVec2& pos) +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + ImGraphCamera& Camera = Graph.Camera; + + return (pos - Camera.Position) * Camera.Scale + Graph.GetCenter(); +} + +ImVec2 ImNodeGraph::ScreenToGrid(const ImVec2& pos) +{ + ImNodeGraphContext& G = *GImNodeGraph; + IM_ASSERT(G.CurrentGraph); + + ImNodeGraphData& Graph = *G.CurrentGraph; + ImGraphCamera& Camera = Graph.Camera; + return Camera.Position + (pos - Graph.GetCenter()) / Camera.Scale; +} + +ImVec2 ImNodeGraph::ScreenToWindow(const ImVec2 &pos) +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + + return pos - Graph.ScreenPos; +} + +ImVec2 ImNodeGraph::WindowToGrid(const ImVec2 &pos) +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + + return ScreenToGrid(Graph.ScreenPos + pos); +} + +ImVec2 ImNodeGraph::SnapToGrid(const ImVec2 &pos) +{ + // Draw the grid + ImGuiWindow& DrawWindow = *ImGui::GetCurrentWindow(); + ImDrawList& DrawList = *DrawWindow.DrawList; + ImNodeGraphData& Graph = *GImNodeGraph->CurrentGraph; + ImNodeGraphStyle& Style = Graph.Style; + ImGraphCamera& Camera = Graph.Camera; + + const float GridSecondarySize = ImGui::GetFontSize() / Camera.Scale; + + return ImFloor(pos / GridSecondarySize) * GridSecondarySize; +} + +void ImNodeGraph::PushItemWidth(float width) +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + + ImGui::PushItemWidth(Graph.Camera.Scale * width); +} + +const ImObjectList& ImNodeGraph::GetConnections() +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + + return Graph.Connections; +} + +void ImNodeGraph::ResetGraph() +{ + ImNodeGraphContext& G = *GImNodeGraph; + if(G.CurrentGraph == nullptr) return; + + ImNodeGraphData& Graph = *G.CurrentGraph; + + Graph.Reset(); +} + +void ImNodeGraph::ResetGraph(const char* graph) +{ + ImNodeGraphData* Graph = FindGraphByTitle(graph); + if(Graph == nullptr) return; + + Graph->Reset(); +} + +int ImNodeGraph::PushChannels(int count) +{ + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + ImDrawListSplitter& Splitter = DrawList._Splitter; + + + // NOTE: this logic has been lifted from ImDrawListSplitter::Split with slight modifications + // to allow nested splits. The main modification is that we only create new ImDrawChannel + // instances after splitter._Count, instead of over the whole splitter._Channels array like + // the regular ImDrawListSplitter::Split method does. + + const int old_channel_capacity = Splitter._Channels.Size; + // NOTE: _Channels is not resized down, and therefore _Count <= _Channels.size()! + const int old_channel_count = Splitter._Count; + const int requested_channel_count = old_channel_count + count; + if (old_channel_capacity < old_channel_count + count) + { + Splitter._Channels.resize(requested_channel_count); + } + + Splitter._Count = requested_channel_count; + + for (int i = old_channel_count; i < requested_channel_count; ++i) + { + ImDrawChannel& channel = Splitter._Channels[i]; + + // If we're inside the old capacity region of the array, we need to reuse the existing + // memory of the command and index buffers. + if (i < old_channel_capacity) + { + channel._CmdBuffer.resize(0); + channel._IdxBuffer.resize(0); + } + // Else, we need to construct new draw channels. + else + { + IM_PLACEMENT_NEW(&channel) ImDrawChannel(); + } + + { + ImDrawCmd draw_cmd; + draw_cmd.ClipRect = DrawList._ClipRectStack.back(); + draw_cmd.TextureId = DrawList._TextureIdStack.back(); + channel._CmdBuffer.push_back(draw_cmd); + } + } + + return Splitter._Count - count; +} + +void ImNodeGraph::SetChannel(ImGuiID id) +{ + ImDrawList* DrawList = ImGui::GetWindowDrawList(); + ImDrawListSplitter& Splitter = DrawList->_Splitter; + Splitter.SetCurrentChannel(DrawList, static_cast(id)); +} + +void ImNodeGraph::SwapChannel(ImDrawChannel& a, ImDrawChannel& b) +{ + a._CmdBuffer.swap(b._CmdBuffer); + a._IdxBuffer.swap(b._IdxBuffer); +} + +void ImNodeGraph::SortChannels() +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + ImDrawListSplitter& Splitter = DrawList._Splitter; + + Splitter.SetCurrentChannel(&DrawList, 0); + auto& Nodes = Graph.Nodes; + auto& Channels = Splitter._Channels; + int Start = 1; + + for(auto it : Nodes) { Start = ImMin(Start, it.BgChannelIndex); } + + int ChnlCnt = Splitter._Count - Start; + + ImVector Buffer; Buffer.resize(ChnlCnt, { }); + + for(int i = 0, c = 0; i < Nodes.Size(); ++i) + { + if(not Nodes(i)) continue; + + const int swap_idx = Start + (c++ * 2); + ImNodeData& node = Nodes[i]; + + if(node.Graph == nullptr) continue; + + SwapChannel(Buffer[node.BgChannelIndex - Start], Channels[swap_idx]); + SwapChannel(Buffer[node.FgChannelIndex - Start], Channels[swap_idx + 1]); + } + + for(int i = 0; i < ChnlCnt; ++i) + { + SwapChannel(Channels[Start + i], Buffer[i]); + } +} + +bool ImNodeGraph::CheckConnectionValidity(ImGuiID id, ImPinConnection& connection) +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + + ImNodeData* node_a = Graph.Nodes(connection.A.Node) ? &Graph.Nodes[connection.A.Node] : nullptr; + ImNodeData* node_b = Graph.Nodes(connection.B.Node) ? &Graph.Nodes[connection.B.Node] : nullptr; + + if(node_a == nullptr) { CleanupConnection(id, connection); return true; } + if(node_b == nullptr) { CleanupConnection(id, connection); return true; } + + if(connection.A.Direction && node_a->OutputPins(connection.A.Pin) == false) { CleanupConnection(id, connection); return true; } + if(!connection.A.Direction && node_a->InputPins(connection.A.Pin) == false) { CleanupConnection(id, connection); return true; } + + if(connection.B.Direction && node_b->OutputPins(connection.B.Pin) == false) { CleanupConnection(id, connection); return true; } + if(!connection.B.Direction && node_b->InputPins(connection.B.Pin) == false) { CleanupConnection(id, connection); return true; } + + return false; +} + +void ImNodeGraph::CleanupConnection(ImGuiID id, ImPinConnection &connection) +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + + ImNodeData* node_a = Graph.Nodes(connection.A.Node) ? &Graph.Nodes[connection.A.Node] : nullptr; + ImNodeData* node_b = Graph.Nodes(connection.B.Node) ? &Graph.Nodes[connection.B.Node] : nullptr; + + if(node_a) + { + auto& pins = connection.A.Direction ? node_a->OutputPins : node_a->InputPins; + pins[connection.A.Pin].Connections.find_erase(id); + } + + if(node_b) + { + auto& pins = connection.B.Direction ? node_b->OutputPins : node_b->InputPins; + pins[connection.B.Pin].Connections.find_erase(id); + } + + Graph.Connections.Erase(id); +} + + +// Nodes --------------------------------------------------------------------------------------------------------------- + +ImNodeData::ImNodeData() + : Graph(nullptr) + , ID(0) + , Root(0, 0) + , FgChannelIndex(0), BgChannelIndex(0) + , Hovered(false), Active(false) +{ + +} + +ImNodeData::ImNodeData(const ImNodeData& other) + : Graph(other.Graph) + , ID(other.ID) + , Root(other.Root) + , ScreenBounds(other.ScreenBounds) + , BgChannelIndex(other.BgChannelIndex) + , FgChannelIndex(other.FgChannelIndex) + , Hovered(other.Hovered), Active(other.Active) + , Header(other.Header) + , InputPins(other.InputPins) + , OutputPins(other.OutputPins) +{ + +} + +ImNodeData &ImNodeData::operator=(const ImNodeData& other) +{ + if(&other == this) return *this; + + Graph = other.Graph; + ID = other.ID; + Root = other.Root; + ScreenBounds = other.ScreenBounds; + BgChannelIndex = other.BgChannelIndex; + FgChannelIndex = other.FgChannelIndex; + Hovered = other.Hovered; + Active = other.Active; + Header = other.Header; + InputPins = other.InputPins; + OutputPins = other.OutputPins; + + return *this; +} + +void ImNodeGraph::DrawNode(ImNodeData* Node) +{ + if(Node == nullptr) return; + + ImNodeGraphData* Graph = Node->Graph; + ImNodeGraphStyle& Style = Graph->Style; + ImGraphCamera& Camera = Graph->Camera; + ImDrawList& DrawList = *ImGui::GetCurrentWindow()->DrawList; + + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, Style.NodeOutlineThickness * Camera.Scale); + ImGui::PushStyleColor(ImGuiCol_Border, Style.GetColorU32(ImNodeGraphColor_NodeOutline)); + + ImU32 color = Style.GetColorU32(ImNodeGraphColor_NodeBackground); + if(Node->Hovered) color = Style.GetColorU32(ImNodeGraphColor_NodeHoveredBackground); + if(Node->Active) color = Style.GetColorU32(ImNodeGraphColor_NodeActiveBackground); + + // Render Base Frame + ImGui::RenderFrame( + Node->ScreenBounds.Min, Node->ScreenBounds.Max + , color, true, Style.NodeRounding * Camera.Scale + ); + + // Render Header + if(Node->Header()) + { + // Same as base, but clipped + ImGui::PushClipRect(Node->Header->ScreenBounds.Min, Node->Header->ScreenBounds.Max, true); + ImGui::RenderFrame( + Node->ScreenBounds.Min, Node->ScreenBounds.Max + , Node->Header->Color, true, Style.NodeRounding * Camera.Scale + ); + ImGui::PopClipRect(); + + // Border line between header and content + DrawList.AddLine( + { Node->Header->ScreenBounds.Min.x, Node->Header->ScreenBounds.Max.y } + , { Node->Header->ScreenBounds.Max.x, Node->Header->ScreenBounds.Max.y } + , Style.GetColorU32(ImNodeGraphColor_NodeOutline), Style.NodeOutlineThickness * Camera.Scale + ); + } + + if(Graph->Selected.Contains(*Node)) + { + DrawList.AddRect( + Node->ScreenBounds.Min, Node->ScreenBounds.Max + , Style.GetColorU32(ImNodeGraphColor_NodeOutlineSelected) + , Style.NodeRounding * Camera.Scale, 0, Style.NodeOutlineSelectedThickness * Camera.Scale + ); + } + +#ifdef IMNODE_GRAPH_DEBUG_PIN_BOUNDS + + for(ImPinData& pin : Node.InputPins) + { + DrawList.AddRect( + pin.ScreenBounds.Min, pin.ScreenBounds.Max + , ImGui::ColorConvertFloat4ToU32({ 1, 0, 0, 1 }) + , 0, 0, 2 + ); + } + + for(ImPinData& pin : Node.OutputPins) + { + DrawList.AddRect( + pin.ScreenBounds.Min, pin.ScreenBounds.Max + , ImGui::ColorConvertFloat4ToU32({ 1, 0, 0, 1 }) + , 0, 0, 2 + ); + } + +#endif + + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); +} + +bool ImNodeGraph::NodeBehaviour(ImNodeData* Node) +{ + if(Node == nullptr) return false; + + ImNodeGraphData& Graph = *Node->Graph; + + bool is_focus = Graph.FocusedNode == *Node; + + if(Node->Hovered) Graph.HoveredNode = *Node; + if(Node->Hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + Graph.FocusedNode = *Node; + } + + // Select Region + if(Graph.SelectRegionStart()) + { + bool intersect = ImAABB(Graph.GetSelection(), Node->ScreenBounds); + bool checked = Graph.SelectRegion.Contains(*Node); + + if(intersect && !checked) + { + Graph.SelectRegion.Insert(*Node); + Graph.UpdateSelection(*Node); + } + + if(!intersect && checked) + { + Graph.SelectRegion.Erase(*Node); + Graph.UpdateSelection(*Node, false, true); + } + } + + Node->Active = is_focus; + + if(Node->Active) ImGui::SetActiveID(Node->ID, ImGui::GetCurrentWindow()); + + return false; +} + +// Pins ---------------------------------------------------------------------------------------------------------------- + + +ImPinData::ImPinData() + : Node(0) + , ID(0) + , Type(0) + , Direction(ImPinDirection_Input) + , Flags(ImPinFlags_None) + , Hovered(false) +{ } + +void ImNodeGraph::PinHead(ImGuiID id, ImPinData* Pin) +{ + ImNodeGraphData& Graph = *GImNodeGraph->CurrentGraph; + const ImGraphCamera& Camera = Graph.Camera; + const ImNodeGraphStyle& Style = Graph.Style; + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, Style.PinOutlineThickness * Camera.Scale); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{ Style.ItemSpacing, Style.ItemSpacing } * Camera.Scale); + + ImGuiWindow& Window = *ImGui::GetCurrentWindow(); + ImDrawList& DrawList = *Window.DrawList; + ImGuiStyle& ImStyle = ImGui::GetStyle(); + const ImVec2 label_size = ImGui::CalcTextSize("##", NULL, true); + + // Modified Radio Button to get proper framing + const float square_sz = ImGui::GetFrameHeight(); + const ImVec2 pos = Window.DC.CursorPos; + const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); + const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? ImStyle.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + ImStyle.FramePadding.y * 2.0f)); + Pin->Center = check_bb.GetCenter(); + const float radius = Style.PinRadius * Camera.Scale; + const float outline = Style.PinOutlineThickness * Camera.Scale; + + // Behaviour + bool pressed = false, filled = !Pin->Connections.empty(); + if(ImGui::IsWindowFocused()) + { + Pin->Hovered = ImGui::IsMouseHoveringRect(check_bb.Min, check_bb.Max); + pressed = (Pin->Hovered && ImGui::IsMouseDown(ImGuiMouseButton_Left)); + filled |= (Pin->Hovered || Graph.NewConnection == *Pin); + + // Start new connection when left clicked + if(Pin->Hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGui::IsAnyModKeyDown()) + { + BeginConnection(*Pin); + ImGui::SetActiveID(id, ImGui::GetCurrentWindow()); + } + + // Dropping new connection + if(Pin->Hovered && Graph.NewConnection() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + ImPinData* other = Graph.FindPin(Graph.NewConnection); + + MakeConnection(*Pin, *other); + } + + // Break connections with Alt-Left-Click + if(Pin->Hovered && ImGui::IsMouseReleased(ImGuiMouseButton_Left) && ImGui::IsKeyDown(ImGuiMod_Alt) && !Graph.NewConnection()) + BreakConnections(*Pin); + } + + + // Item for ImGui + ImGui::ItemSize(total_bb, ImStyle.FramePadding.y); + ImGui::ItemAdd(total_bb, id, &check_bb); + ImGui::ItemHoverable(check_bb, id, ImGuiHoveredFlags_None); + + // Drawing + ImVec4 PinColor = Style.PinColors[Pin->Type].Value; + PinColor = PinColor * (pressed ? 0.8f : 1.0f); + ImVec4 FillColor = filled ? PinColor : Style.GetColorVec4(ImNodeGraphColor_PinBackground); + + if(pressed || filled) + { + DrawList.AddCircleFilled(Pin->Center, radius + outline * 0.5f, ImGui::ColorConvertFloat4ToU32(FillColor)); + } + else + { + DrawList.AddCircleFilled(Pin->Center, radius, ImGui::ColorConvertFloat4ToU32(FillColor)); + DrawList.AddCircle(Pin->Center, radius, ImGui::ColorConvertFloat4ToU32(PinColor), 0, outline); + } + + ImGui::SameLine(); + ImGui::PopStyleVar(2); +} + +void ImNodeGraph::DummyPinHead(ImPinData* Pin) +{ + if(Pin == nullptr) return; + + ImNodeGraphData* Graph = GImNodeGraph->CurrentGraph; + const ImGraphCamera& Camera = Graph->Camera; + const ImNodeGraphStyle& Style = Graph->Style; + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, Style.PinOutlineThickness * Camera.Scale); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{ Style.ItemSpacing, Style.ItemSpacing } * Camera.Scale); + + ImGuiWindow& Window = *ImGui::GetCurrentWindow(); + ImGuiStyle& ImStyle = ImGui::GetStyle(); + const ImVec2 label_size = ImGui::CalcTextSize("##", NULL, true); + + const float square_sz = ImGui::GetFrameHeight(); + const ImVec2 pos = Window.DC.CursorPos; + const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? ImStyle.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + ImStyle.FramePadding.y * 2.0f)); + + ImGui::ItemSize(total_bb, ImStyle.FramePadding.y); + ImGui::ItemAdd(total_bb, -1); + + ImGui::SameLine(); + ImGui::PopStyleVar(2); +} + + +// Connections --------------------------------------------------------------------------------------------------------- + +void ImNodeGraph::BeginConnection(const ImPinPtr &pin) +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + Graph.NewConnection = pin; +} + +bool ImNodeGraph::Exists(ImPinConnection connection) +{ + const ImPinPtr &a = connection.A, &b = connection.B; + if(a.Direction == b.Direction) return false; + if(a.Node == b.Node) return false; + + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + + ImPinData* A = Graph.FindPin(a); + + if(A == nullptr) return false; + + bool result = false; + for(ImGuiID cid : A->Connections) + { + const ImPinConnection& conn = Graph.Connections[cid]; + result |= (conn.A == a && conn.B == b); + result |= (conn.A == b && conn.B == a); + + if(result) return result; + } + + return false; +} + +bool ImNodeGraph::MakeConnection(const ImPinPtr &a, const ImPinPtr &b) +{ + if(a.Direction == b.Direction) return false; + if(a.Node == b.Node) return false; + + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + + if(Exists({ a, b })) return false; + + ImPinData* A = Graph.FindPin(a); + ImPinData* B = Graph.FindPin(b); + + if(A == nullptr || B == nullptr) return false; + + if(Graph.Validation && not Graph.Validation(*A, *B)) return false; + + if(A->Direction == ImPinDirection_Input && !A->Connections.empty()) BreakConnections(*A); + if(B->Direction == ImPinDirection_Input && !B->Connections.empty()) BreakConnections(*B); + + ImGuiID connId = Graph.Connections.Insert({ a, b }); + + A->Connections.push_back(connId); + B->Connections.push_back(connId); + + A->NewConnections.push_back(b); A->BNewConnections = true; + B->NewConnections.push_back(a); B->BNewConnections = true; + + return true; +} + +bool ImNodeGraph::MakeConnection(const ImUserPinPtr& a, const ImUserPinPtr& b) +{ + if(a.Direction == b.Direction) return false; + if(a.Node == b.Node) return false; + + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + + ImPinData* A = Graph.FindPin(a); + ImPinData* B = Graph.FindPin(b); + + if(A == nullptr || B == nullptr) return false; + + if(Exists({ *A, *B })) return false; + + if(Graph.Validation && not Graph.Validation(*A, *B)) return false; + + if(A->Direction == ImPinDirection_Input && !A->Connections.empty()) BreakConnections(*A); + if(B->Direction == ImPinDirection_Input && !B->Connections.empty()) BreakConnections(*B); + + ImGuiID connId = Graph.Connections.Insert({ *A, *B }); + + A->Connections.push_back(connId); + B->Connections.push_back(connId); + + A->NewConnections.push_back(*B); A->BNewConnections = true; + B->NewConnections.push_back(*A); B->BNewConnections = true; + + return true; +} + +void ImNodeGraph::BreakConnection(ImGuiID id) +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + + ImPinConnection connection = Graph.Connections[id]; Graph.Connections.Erase(id); + ImPinData* A = Graph.FindPin(connection.A); + ImPinData* B = Graph.FindPin(connection.B); + + if(A == nullptr || B == nullptr) return; + + A->Connections.find_erase_unsorted(id); + B->Connections.find_erase_unsorted(id); + + A->ErasedConnections.push_back(connection.B); A->BErasedConnections = true; + B->ErasedConnections.push_back(connection.A); B->BErasedConnections = true; + + ImPinPtr* it; + if((it = A->NewConnections.find(*B)) != A->NewConnections.end()) A->NewConnections.erase(it); + if((it = B->NewConnections.find(*A)) != B->NewConnections.end()) B->NewConnections.erase(it); +} + +void ImNodeGraph::BreakConnections(const ImPinPtr &ptr) +{ + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData& Graph = *G.CurrentGraph; + ImPinData* Pin = Graph.FindPin(ptr); + + if(Pin == nullptr) return; + + for(ImGuiID id : Pin->Connections) + { + ImPinConnection connection = Graph.Connections[id]; Graph.Connections.Erase(id); + ImPinData* other = Graph.FindPin((connection.A == ptr) ? connection.B : connection.A); + + Pin->ErasedConnections.push_back(*other); Pin->BErasedConnections = true; + other->ErasedConnections.push_back(ptr); other->BErasedConnections = true; + + ImPinPtr* it; + if((it = Pin->NewConnections.find(*other)) != Pin->NewConnections.end()) Pin->NewConnections.erase(it); + if((it = other->NewConnections.find(*Pin)) != other->NewConnections.end()) other->NewConnections.erase(it); + + other->Connections.find_erase_unsorted(id); + } + + Pin->Connections.clear(); +} + +ImPinConnection ImNodeGraph::GetConnection(ImGuiID connection) +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + return Graph->Connections[connection]; +} + +void ImNodeGraph::DrawConnection(const ImVec2& out, const ImVec4& out_col, const ImVec2& in, const ImVec4& in_col) +{ + ImGuiWindow& Window = *ImGui::GetCurrentWindow(); + ImDrawList& DrawList = *Window.DrawList; + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + const ImGraphCamera& Camera = Graph->Camera; + const ImNodeGraphStyle& Style = Graph->Style; + + // Calculate Bezier Derivatives + const float FrameHeight = ImGui::GetFrameHeight(); + const float diff_x = out.x - in.x; + const float diff_y = out.y - in.y; + const float y_weight = ImAbs(diff_y); + const float xy_ratio = 1.0f + ImMax(diff_x, 0.0f) / (FrameHeight + ImAbs(diff_y)); + const float offset = y_weight * xy_ratio; + + const ImVec2 out_v = ImVec2(out.x + offset, out.y); + const ImVec2 in_v = ImVec2(in.x - offset, in.y); + + AddBezierCubicMultiColored(DrawList, in, in_v, out_v, out, in_col, out_col, Style.ConnectionThickness * Camera.Scale); +} + +void ImNodeGraph::DrawConnection(const ImPinData* pin, const ImVec2& point) +{ + if(pin == nullptr) return; + + ImNodeGraphData* Graph = GImNodeGraph->CurrentGraph; + const ImNodeGraphStyle& Style = Graph->Style; + + if(pin->Direction) DrawConnection(PinConnectionAnchor(pin), Style.PinColors[pin->Type], point, Style.PinColors[pin->Type]); + else DrawConnection(point, Style.PinColors[pin->Type], PinConnectionAnchor(pin), Style.PinColors[pin->Type]); +} + +void ImNodeGraph::DrawConnection(const ImPinData *a, const ImPinData *b) +{ + ImNodeGraphData* Graph = GImNodeGraph->CurrentGraph; + const ImNodeGraphStyle& Style = Graph->Style; + + const ImVec2& a_anchor = PinConnectionAnchor(a); + const ImVec4& a_col = Style.PinColors[a->Type]; + const ImVec2& b_anchor = PinConnectionAnchor(b); + const ImVec4& b_col = Style.PinColors[b->Type]; + + const ImVec2& out = a->Direction ? a_anchor : b_anchor; + const ImVec2& in = a->Direction ? b_anchor : a_anchor; + + const ImVec4& out_col = a->Direction ? a_col : b_col; + const ImVec4& in_col = a->Direction ? b_col : a_col; + + DrawConnection(out, out_col, in, in_col); +} + +ImVec2 ImNodeGraph::PinConnectionAnchor(const ImPinData *Pin) +{ + ImNodeGraphData* Graph = GImNodeGraph->CurrentGraph; + const ImGraphCamera& Camera = Graph->Camera; + const ImNodeGraphStyle& Style = Graph->Style; + const float radius = Style.PinRadius * Camera.Scale; + ImVec2 loc = Pin->Center; + loc += ImVec2(radius, 0) * (Pin->Direction ? 1.0f : -1.0f); + return loc; +} + +// On AddPolyline() and AddConvexPolyFilled() we intentionally avoid using ImVec2 and superfluous function calls to optimize debug/non-inlined builds. +// - Those macros expects l-values and need to be used as their own statement. +// - Those macros are intentionally not surrounded by the 'do {} while (0)' idiom because even that translates to runtime with debug compilers. +#define IM_NORMALIZE2F_OVER_ZERO(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.0f) { float inv_len = ImRsqrt(d2); VX *= inv_len; VY *= inv_len; } } (void)0 +#define IM_FIXNORMAL2F_MAX_INVLEN2 100.0f // 500.0f (see #4053, #3366) +#define IM_FIXNORMAL2F(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.000001f) { float inv_len2 = 1.0f / d2; if (inv_len2 > IM_FIXNORMAL2F_MAX_INVLEN2) inv_len2 = IM_FIXNORMAL2F_MAX_INVLEN2; VX *= inv_len2; VY *= inv_len2; } } (void)0 + +void ImNodeGraph::AddPolylineMultiColored(ImDrawList &draw_list, const ImVec2 *points, int num_points, const ImVec4 &c1, + const ImVec4 &c2, ImDrawFlags flags, float thickness) +{ + if (num_points < 2) return; + + const int count = num_points - 1; // The number of line segments we need to draw + const bool thick_line = (thickness > draw_list._FringeScale); + const ImVec2 opaque_uv = draw_list._Data->TexUvWhitePixel; + + draw_list._Data->TempBuffer.reserve_discard(num_points * 2); + ImVec2* normals = draw_list._Data->TempBuffer.Data; + ImU32* colors = reinterpret_cast(normals + num_points); + for(int i = 0; i < count; ++i) + { + const ImVec2 &a = points[i], &b = points[(i + 1) % num_points]; + normals[i] = b - a; + IM_NORMALIZE2F_OVER_ZERO(normals[i].x, normals[i].y); + normals[i] = { normals[i].y, -normals[i].x }; + colors[i] = ImGui::ColorConvertFloat4ToU32({ ImLerp(c1, c2, i / static_cast(num_points)) }); + } + colors[num_points - 1] = ImGui::ColorConvertFloat4ToU32(c2); + normals[num_points - 1] = normals[num_points - 2]; + + for(int i = 1; i < count; ++i) + { + normals[i] = (normals[i] + normals[i + 1]) * 0.5f; + IM_FIXNORMAL2F(normals[i].x, normals[i].y); + } + + const float AA_SIZE = draw_list._FringeScale; + + // Thicknesses <1.0 should behave like thickness 1.0 + thickness = ImMax(thickness, 1.0f); + const int integer_thickness = (int)thickness; + const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; + + const int idx_count = thick_line ? count * 18 : count * 12; + const int vtx_count = thick_line ? num_points * 4 : num_points * 3; + draw_list.PrimReserve(idx_count, vtx_count); + + ImDrawIdx* _IdxWritePtr = draw_list._IdxWritePtr; + ImDrawVert* _VtxWritePtr = draw_list._VtxWritePtr; + + for(int i = 0; i <= count; ++i) + { + if(thick_line) + { + const int v1 = draw_list._VtxCurrentIdx + i * 4; + const int v2 = draw_list._VtxCurrentIdx + i * 4 + 4; + + const int i1 = i; + + const ImVec2 n1 = normals[i1] * (half_inner_thickness + AA_SIZE); + const ImVec2 n2 = normals[i1] * (half_inner_thickness); + + // first points + _VtxWritePtr[0].pos = points[i1] + n1; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = colors[i1] & ~IM_COL32_A_MASK; + _VtxWritePtr[1].pos = points[i1] + n2; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = colors[i1]; + _VtxWritePtr[2].pos = points[i1] - n2; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = colors[i1]; + _VtxWritePtr[3].pos = points[i1] - n1; _VtxWritePtr[3].uv = opaque_uv; _VtxWritePtr[3].col = colors[i1] & ~IM_COL32_A_MASK; + + _VtxWritePtr += 4; + + if(i == count) continue; + + // top + _IdxWritePtr[0] = v1 + 0; _IdxWritePtr[1] = v2 + 0; _IdxWritePtr[2] = v1 + 1; + _IdxWritePtr[3] = v1 + 1; _IdxWritePtr[4] = v2 + 0; _IdxWritePtr[5] = v2 + 1; + + // middle + _IdxWritePtr[6] = v1 + 1; _IdxWritePtr[7] = v2 + 1; _IdxWritePtr[8] = v1 + 2; + _IdxWritePtr[9] = v1 + 2; _IdxWritePtr[10] = v2 + 1; _IdxWritePtr[11] = v2 + 2; + + // bottom + _IdxWritePtr[12] = v1 + 2; _IdxWritePtr[13] = v2 + 2; _IdxWritePtr[14] = v1 + 3; + _IdxWritePtr[15] = v1 + 3; _IdxWritePtr[16] = v2 + 2; _IdxWritePtr[17] = v2 + 3; + + _IdxWritePtr += 18; + } + else + { + const int v1 = draw_list._VtxCurrentIdx + i * 4; + const int v2 = draw_list._VtxCurrentIdx + i * 4 + 4; + + const int i1 = i; + + const ImVec2 n = normals[i1] * AA_SIZE; + + // first points + _VtxWritePtr[0].pos = points[i1] + n; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = colors[i1] & ~IM_COL32_A_MASK; + _VtxWritePtr[1].pos = points[i1]; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = colors[i1]; + _VtxWritePtr[2].pos = points[i1] - n; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = colors[i1] & ~IM_COL32_A_MASK; + + _VtxWritePtr += 3; + + if(i == count) continue; + + // top + _IdxWritePtr[0] = v1 + 0; _IdxWritePtr[1] = v2 + 0; _IdxWritePtr[2] = v1 + 1; + _IdxWritePtr[3] = v1 + 1; _IdxWritePtr[4] = v2 + 0; _IdxWritePtr[5] = v2 + 1; + + // bottom + _IdxWritePtr[6] = v1 + 1; _IdxWritePtr[7] = v2 + 1; _IdxWritePtr[8] = v1 + 2; + _IdxWritePtr[9] = v1 + 2; _IdxWritePtr[10] = v2 + 1; _IdxWritePtr[11] = v2 + 2; + + _IdxWritePtr += 12; + } + } + + draw_list._VtxCurrentIdx += static_cast(_VtxWritePtr - draw_list._VtxWritePtr); + draw_list._VtxWritePtr = _VtxWritePtr; + draw_list._IdxWritePtr = _IdxWritePtr; +} + +#define IM_FIXNORMAL2F_MAX_INVLEN2 100.0f // 500.0f (see #4053, #3366) + +#define IM_FIXNORMAL2F(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.000001f) { float inv_len2 = 1.0f / d2; if (inv_len2 > IM_FIXNORMAL2F_MAX_INVLEN2) inv_len2 = IM_FIXNORMAL2F_MAX_INVLEN2; VX *= inv_len2; VY *= inv_len2; } } (void)0 + +void ImNodeGraph::AddBezierCubicMultiColored(ImDrawList& draw_list, const ImVec2 &p1, const ImVec2 &p2, const ImVec2 &p3, const ImVec2 &p4, + const ImVec4 &c1, const ImVec4 &c2, float thickness, int num_segments) +{ + draw_list.PathLineTo(p1); + draw_list.PathBezierCubicCurveTo(p2, p3, p4, num_segments); + PathStrokeMultiColored(draw_list, c1, c2, 0, thickness); +} + + +// ===================================================================================================================== +// Public Functionality +// ===================================================================================================================== + +// Context ------------------------------------------------------------------------------------------------------------- + +ImNodeGraphContext* ImNodeGraph::CreateContext() +{ + // Get current context + ImNodeGraphContext* prev_ctx = GetCurrentContext(); + + // Create new context + ImNodeGraphContext* ctx = IM_NEW(ImNodeGraphContext)(); + SetCurrentContext(ctx); + Initialize(); + + // If there is a previous context, restore it + if(prev_ctx != nullptr) SetCurrentContext(prev_ctx); + + // Return the new context + return ctx; +} + +void ImNodeGraph::DestroyContext(ImNodeGraphContext* ctx) +{ + // Get current context + ImNodeGraphContext* prev_ctx = GetCurrentContext(); + + // If the provided context is null, use the current context + if(ctx == nullptr) ctx = prev_ctx; + + // Shutdown the context to destroy + SetCurrentContext(ctx); + Shutdown(); + + // Restore or clear the context + SetCurrentContext((prev_ctx == ctx) ? nullptr : prev_ctx); + + // Free context memory + IM_DELETE(ctx); +} + +ImNodeGraphContext * ImNodeGraph::GetCurrentContext() +{ + return GImNodeGraph; +} + +void ImNodeGraph::SetCurrentContext(ImNodeGraphContext *ctx) +{ + GImNodeGraph = ctx; +} + +void ImNodeGraph::AddFont(const char * path, float size, const ImWchar* glyph_ranges) +{ + ImNodeFontConfig cfg{ ImStrdup(path), size, glyph_ranges }; + GFonts.push_back(cfg); +} + + +// Graph --------------------------------------------------------------------------------------------------------------- + +ImGraphCamera::ImGraphCamera() : Position{ 0, 0 }, Scale(1.0f) {} + +ImNodeGraphStyle::ImNodeGraphStyle() + : GridPrimaryStep(5) + , GridPrimaryThickness(2.0f) + , GridSecondaryThickness(1.0f) + + , NodePadding(8.0f) + , NodeRounding(8.0f) + , NodeOutlineThickness(2.0f) + , NodeOutlineSelectedThickness(4.0f) + + , SelectRegionRounding(2.0f) + , SelectRegionOutlineThickness(2.0f) + + , ItemSpacing(4.0f) + , PinRadius(8.0f) + , PinOutlineThickness(3.0f) + + , ConnectionThickness(2.0f) + + , Colors{ ImColor(0x000000FF) } + , PinColors(nullptr) +{ + Colors[ImNodeGraphColor_GridBackground] = ImColor(0x11, 0x11, 0x11); + Colors[ImNodeGraphColor_GridPrimaryLines] = ImColor(0x88, 0x88, 0x88); + Colors[ImNodeGraphColor_GridSecondaryLines] = ImColor(0x44, 0x44, 0x44); + + Colors[ImNodeGraphColor_NodeBackground] = ImColor(0x88, 0x88, 0x88); + Colors[ImNodeGraphColor_NodeHoveredBackground] = ImColor(0x9C, 0x9C, 0x9C); + Colors[ImNodeGraphColor_NodeActiveBackground] = ImColor(0x7A, 0x7A, 0x7A); + Colors[ImNodeGraphColor_NodeOutline] = ImColor(0x33, 0x33, 0x33); + Colors[ImNodeGraphColor_NodeOutlineSelected] = ImColor(0xEF, 0xAE, 0x4B); + + Colors[ImNodeGraphColor_PinBackground] = ImColor(0x22, 0x22, 0x22); + + Colors[ImNodeGraphColor_SelectRegionBackground] = ImColor(0xC9, 0x8E, 0x36, 0x44); + Colors[ImNodeGraphColor_SelectRegionOutline] = ImColor(0xEF, 0xAE, 0x4B, 0xBB); +} + +ImNodeGraphSettings::ImNodeGraphSettings() + : ZoomRate(0.1f) + , ZoomSmoothing(8.0f) + , ZoomBounds(0.6f, 2.5f) +{ + +} + +void ImNodeGraph::BeginGraph(const char* title, const ImVec2& size_arg) +{ + // Validate Global State + IM_ASSERT(GImNodeGraph != nullptr); + ImNodeGraphContext& G = *GImNodeGraph; + + // Ensure we are in the scope of a window + ImGuiWindow* Window = ImGui::GetCurrentWindow(); + IM_ASSERT(Window != nullptr); // Ensure we are within a window + + // Validate parameters and graph state + IM_ASSERT(title != nullptr && title[0] != '\0'); // Graph name required + IM_ASSERT(G.Scope == ImNodeGraphScope_None); // Ensure we are not in the scope of another graph + + // Get Graph + ImNodeGraphData* Graph = FindGraphByTitle(title); + const bool FirstFrame = (Graph == nullptr); + if(FirstFrame) { Graph = CreateNewGraph(title); } + + ImGraphCamera& Camera = Graph->Camera; + + // Update State + G.CurrentGraph = Graph; + G.Scope = ImNodeGraphScope_Graph; + + // Style & Settings + ImNodeGraphStyle& Style = Graph->Style; + + // Fonts + G.Fonts.front()->Scale = Camera.Scale / GFontUpscale; + ImGui::PushFont(G.Fonts.front()); + + // Calculate Size + const ImVec2 SizeAvail = ImGui::GetContentRegionAvail(); + const ImVec2 Size = ImGui::CalcItemSize(size_arg, SizeAvail.x, SizeAvail.y); + Graph->ScreenSize = Size; + Graph->ScreenPos = ImGui::GetCursorScreenPos(); + Graph->SubmitCount = 0; + Graph->LockSelectRegion = false; + + // Cleanup erased nodes + int cnt = Graph->Nodes.Cleanup(); + for(int i = ImMax(Graph->Nodes.Freed.Size - cnt - 1, 0); i < Graph->Nodes.Freed.Size; ++i) + { + Graph->Selected.Erase(Graph->Nodes.IdxToID[Graph->Nodes.Freed[i]]); + } + + // Reset nodes + Graph->Nodes.Reset(); + + // Begin the Graph Child + ImGui::PushStyleColor(ImGuiCol_ChildBg, static_cast(Style.Colors[ImNodeGraphColor_GridBackground])); + ImGui::BeginChild(Graph->ID, Size, 0, ImGuiWindowFlags_NoScrollbar); + ImGui::PopStyleColor(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2{ Style.ItemSpacing, Style.ItemSpacing } * Camera.Scale); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2{ Style.ItemSpacing, Style.ItemSpacing } * Camera.Scale); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{ Style.NodePadding, Style.NodePadding } * Camera.Scale); + DrawGrid({ Graph->ScreenPos, Graph->ScreenPos + Graph->ScreenSize }); +} + +void ImNodeGraph::EndGraph() +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + ImNodeGraphContext& G = *GImNodeGraph; + + // Validate graph state + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(G.Scope == ImNodeGraphScope_Graph && Graph != nullptr); // Ensure we are in the scope of a graph + + DrawGraph(Graph); + + if(ImGui::IsWindowFocused()) GraphBehaviour({ Graph->ScreenPos, Graph->ScreenPos + Graph->ScreenSize }); + + ImGui::PopStyleVar(3); + ImGui::PopFont(); + ImGui::EndChild(); + + // Update State + G.CurrentGraph = nullptr; + G.Scope = ImNodeGraphScope_None; +} + +bool ImNodeGraph::BeginGraphPostOp(const char *title) +{ + // Validate Global State + IM_ASSERT(GImNodeGraph != nullptr); + ImNodeGraphContext& G = *GImNodeGraph; + + // Ensure we are in the scope of a window + ImGuiWindow* Window = ImGui::GetCurrentWindow(); + IM_ASSERT(Window != nullptr); // Ensure we are within a window + + // Validate parameters and graph state + IM_ASSERT(title != nullptr && title[0] != '\0'); // Graph name required + IM_ASSERT(G.Scope == ImNodeGraphScope_None); // Ensure we are not in the scope of another graph + + // Get Graph + ImNodeGraphData* Graph = FindGraphByTitle(title); + + if(Graph == nullptr) return false; + + // Update State + G.CurrentGraph = Graph; + G.Scope = ImNodeGraphScope_Graph; + + return true; +} + +void ImNodeGraph::EndGraphPostOp() +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + ImNodeGraphContext& G = *GImNodeGraph; + + // Validate graph state + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(G.Scope == ImNodeGraphScope_Graph && Graph != nullptr); // Ensure we are in the scope of a graph + + // Update State + G.CurrentGraph = nullptr; + G.Scope = ImNodeGraphScope_None; +} + +void ImNodeGraph::SetGraphValidation(ImConnectionValidation validation) +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + ImNodeGraphContext& G = *GImNodeGraph; + + // Validate graph state + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(G.Scope != ImNodeGraphScope_None && Graph != nullptr); // Ensure we are in the scope of a graph + + Graph->Validation = validation; +} + +float ImNodeGraph::GetCameraScale() +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + ImNodeGraphContext& G = *GImNodeGraph; + + // Validate graph state + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(G.Scope != ImNodeGraphScope_None && Graph != nullptr); // Ensure we are in the scope of a graph + + return Graph->Camera.Scale; +} + +void ImNodeGraph::BeginNode(int iid, ImVec2& pos) +{ + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate State + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(G.Scope == ImNodeGraphScope_Graph && Graph != nullptr); // Ensure we are in the scope of a graph + + // Get Node + ImGuiID id = ImGui::GetCurrentWindow()->GetID(iid); + ImNodeData& Node = Graph->Nodes[id]; + if(Node.Graph == nullptr) + { + Node.Graph = Graph; + Node.Root = pos; + Node.ID = id; + Node.UserID = iid; + } + + // Style + const ImNodeGraphStyle& Style = Graph->Style; + + // Update node vars + Node.InputPins.Cleanup(); Node.InputPins.Reset(); + Node.OutputPins.Cleanup(); Node.OutputPins.Reset(); + Node.Header.Reset(); + pos = Node.Root; + + // Push Scope + Graph->CurrentNode = &Node; + Graph->SubmitCount ++; + G.Scope = ImNodeGraphScope_Node; + + // Push new draw channels + Node.BgChannelIndex = PushChannels(2); + Node.FgChannelIndex = Node.BgChannelIndex + 1; + SetChannel(Node.FgChannelIndex); + + // Setup Node Group + ImGui::SetCursorScreenPos(GridToScreen(pos + ImVec2(Style.NodePadding, Style.NodePadding))); + ImGui::BeginGroup(); + ImGui::PushID(static_cast(id)); + + ImGuiContext& Ctx = *ImGui::GetCurrentContext(); + Node.PrevActiveItem = Ctx.ActiveId; +} + +void ImNodeGraph::EndNode() +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + IM_ASSERT(G.Scope == ImNodeGraphScope_Node && Graph->CurrentNode != nullptr); // Ensure we are in the scope of a node + + ImNodeData& Node = *Graph->CurrentNode; + ImGuiContext& Ctx = *ImGui::GetCurrentContext(); + if(Ctx.ActiveId != Node.PrevActiveItem || Ctx.ActiveId == 0) Node.ActiveItem = Ctx.ActiveId; + + bool is_node_item_active = Ctx.ActiveId == Node.ActiveItem && Ctx.ActiveId != 0; + bool other_hovered = ImGui::IsAnyItemHovered() || is_node_item_active; + if(other_hovered) Graph->LockSelectRegion = true; + + ImGui::PopID(); + ImGui::EndGroup(); + + const ImNodeGraphStyle& Style = Graph->Style; + const ImGraphCamera& Camera = Graph->Camera; + + Node.ScreenBounds = { ImGui::GetItemRectMin(), ImGui::GetItemRectMax() }; + Node.ScreenBounds.Expand(Style.NodePadding * Camera.Scale); + + bool hovering = ImGui::IsMouseHoveringRect(Node.ScreenBounds.Min, Node.ScreenBounds.Max) && !other_hovered; + bool is_focus = Graph->FocusedNode == Node; + bool is_hovered = Graph->HoveredNode == Node; + + // Fixup pins + float Width = Node.Header->ScreenBounds.GetWidth() - Style.NodePadding * Camera.Scale; + auto Input = Node.InputPins.begin(); + auto Output = Node.OutputPins.begin(); + const auto InputEnd = Node.InputPins.end(); + const auto OutputEnd = Node.OutputPins.end(); + + while(Input != InputEnd || Output != OutputEnd) + { + float iWidth = Input != InputEnd ? Input->ScreenBounds.GetWidth() : 0; + float oWidth = Output != OutputEnd ? Output->ScreenBounds.GetWidth() : 0; + Width = ImMax(Width, iWidth + oWidth); + + if(Input != InputEnd) { if(Input->Hovered) hovering = false; ++Input; } + if(Output != OutputEnd) { if(Output->Hovered) hovering = false; ++Output; } + } + + Node.Hovered = hovering; // Whether mouse is over node + Node.Hovered &= !Graph->HoveredNode() || is_hovered; // Check if a node later in the draw order is hovered + Node.Hovered &= !Graph->FocusedNode() || is_focus; // Chech if another node is focused + Node.Hovered &= !Graph->SelectRegionStart(); // Check for drag selection + + // Pop Scope + G.Scope = ImNodeGraphScope_Graph; + Graph->CurrentNode = nullptr; + + // fix up header width + if(Node.Header()) + { + Node.Header->ScreenBounds.Min.x = Node.ScreenBounds.Min.x; + Node.Header->ScreenBounds.Max.x = Node.ScreenBounds.Max.x; + } + + Input = Node.InputPins.begin(); + Output = Node.OutputPins.begin(); + float Y = Node.Header->ScreenBounds.Max.y + (Node.PinOffset + Style.NodePadding) * Camera.Scale; + float InX = Node.ScreenBounds.Min.x + Style.NodePadding * Camera.Scale; + + while(Input != InputEnd || Output != OutputEnd) + { + float Step = 0.0f; + if(Input != InputEnd) + { + Input->Pos = { InX, Y }; + Step = ImMax(Step, Input->ScreenBounds.GetHeight()); + ++Input; + } + + if(Output != OutputEnd) + { + float OutX = InX + Width - Output->ScreenBounds.GetWidth(); + Output->Pos = { OutX, Y }; + Step = ImMax(Step, Output->ScreenBounds.GetHeight()); + ++Output; + } + + Y += Step + Style.ItemSpacing; + } +} + +void ImNodeGraph::BeginNodeHeader(const char *title, ImColor color, ImColor hovered, ImColor active) +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + // Validate node scope + ImNodeData* Node = Graph->CurrentNode; + IM_ASSERT(G.Scope == ImNodeGraphScope_Node && Node != nullptr); // Ensure we are in the scope of a node + IM_ASSERT(Node->Header() == false); // Ensure there is only one header + + if(Node->Hovered) color = hovered; + if(Node->Active) color = active; + + // Setup header + Node->Header = ImNodeHeaderData{ + Node + , color + , ImRect() + }; + + // Create group + ImGui::BeginGroup(); + ImGui::PushID(title); + + // Push scope + G.Scope = ImNodeGraphScope_NodeHeader; +} + +void ImNodeGraph::BeginNodeHeader(int id, ImColor color, ImColor hovered, ImColor active) +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + // Validate node scope + ImNodeData* Node = Graph->CurrentNode; + IM_ASSERT(G.Scope == ImNodeGraphScope_Node && Node != nullptr); // Ensure we are in the scope of a node + IM_ASSERT(Node->Header() == false); // Ensure there is only one header + + if(Node->Hovered) color = hovered; + if(Node->Active) color = active; + + // Setup header + Node->Header = ImNodeHeaderData{ + Node + , color + , ImRect() + }; + + // Create group + ImGui::BeginGroup(); + ImGui::PushID(id); + + // Push scope + G.Scope = ImNodeGraphScope_NodeHeader; +} + +void ImNodeGraph::EndNodeHeader() +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + // Validate node scope + ImNodeData* Node = Graph->CurrentNode; + IM_ASSERT(G.Scope == ImNodeGraphScope_NodeHeader && Node != nullptr); // Ensure we are in the scope of a node + IM_ASSERT(Node->Header()); // Ensure the header is valid + + // End Group + ImGui::PopID(); + ImGui::EndGroup(); + + const ImNodeGraphStyle& Style = Graph->Style; + const ImGraphCamera& Camera = Graph->Camera; + Node->Header->ScreenBounds = { ImGui::GetItemRectMin(), ImGui::GetItemRectMax() }; + Node->Header->ScreenBounds.Expand(Style.NodePadding * Camera.Scale); + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + Style.NodePadding * Camera.Scale); + + G.Scope = ImNodeGraphScope_Node; +} + +ImSet* ImNodeGraph::GetSelected() +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + return &Graph->Selected; +} + +ImSet* ImNodeGraph::GetSelected(const char *title) +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = FindGraphByTitle(title); + + return &Graph->Selected; +} + +int ImNodeGraph::GetUserID(ImGuiID id) +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + IM_ASSERT(GImNodeGraph->CurrentGraph != nullptr); + + ImNodeData& Node = GImNodeGraph->CurrentGraph->Nodes[id]; + if(Node.Graph == nullptr) return -1; + return Node.UserID; +} + +int ImNodeGraph::GetUserID(const char* graph, ImGuiID id) +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = FindGraphByTitle(graph); + + return Graph->Nodes[id].UserID; +} + +void ImNodeGraph::SetPinColors(const ImColor *colors) +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + Graph->Style.PinColors = colors; +} + +bool ImNodeGraph::BeginPin(int iid, ImPinType type, ImPinDirection direction, ImPinFlags flags) +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + // Validate node scope + ImNodeData* Node = Graph->CurrentNode; + IM_ASSERT(G.Scope == ImNodeGraphScope_Node && Node != nullptr); // Ensure we are in the scope of a node + + const ImNodeGraphStyle& Style = Graph->Style; + const ImGraphCamera& Camera = Graph->Camera; + + // Get Pin Offset + if(not Node->PinOffset()) + { + float screen_y = WindowToScreen(ImGui::GetCursorPos()).y; + if(Node->Header()) Node->PinOffset = (screen_y - Node->Header->ScreenBounds.Max.y); + else Node->PinOffset = (screen_y - Node->ScreenBounds.Min.y); + } + + // Push the pin + ImGuiID id = ImGui::GetCurrentWindow()->GetID(iid); + ImPinData* Pin = &(direction ? Node->OutputPins[id] : Node->InputPins[id]); + Graph->CurrentPin = Pin; + + bool changed = false; + changed |= !Pin->NewConnections.empty(); + changed |= !Pin->ErasedConnections.empty(); + + Pin->BNewConnections = false; + Pin->BErasedConnections = false; + + // Setup pin + Pin->Node = Node->ID; + Pin->ID = id; + Pin->UserID = iid; + Pin->Type = type; + Pin->Direction = direction; + Pin->Flags = flags; + + // Setup ImGui Group + ImGui::SetCursorScreenPos(Pin->Pos); // The first frame the node will be completely garbled + ImGui::BeginGroup(); + ImGui::PushID(static_cast(id)); + + + // Push Scope + G.Scope = ImNodeGraphScope_Pin; + + if(!Pin->Direction) + { + PinHead(id, Pin); + ImGui::SameLine(); + } + else if(!(flags & ImPinFlags_NoPadding)) + { + DummyPinHead(Pin); // Guess this counts as padding + ImGui::SameLine(); + } + + return changed; +} + +void ImNodeGraph::EndPin() +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + // Validate pin scope + ImPinData* Pin = Graph->CurrentPin; + IM_ASSERT(G.Scope == ImNodeGraphScope_Pin && Pin != nullptr); // Ensure we are in the scope of a pin + + if(Pin->Direction) + { + ImGui::SameLine(); + PinHead(Pin->ID, Pin); + } + + ImGui::PopID(); + ImGui::EndGroup(); + + Pin->ScreenBounds = { ImGui::GetItemRectMin(), ImGui::GetItemRectMax() }; + + // Pop Scope + G.Scope = ImNodeGraphScope_Node; + + if(Pin->BNewConnections == false) Pin->NewConnections.clear(); + if(Pin->BErasedConnections == false) Pin->ErasedConnections.clear(); +} + +bool ImNodeGraph::IsPinConnected() +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + // Validate pin scope + ImPinData* Pin = Graph->CurrentPin; + IM_ASSERT(G.Scope == ImNodeGraphScope_Pin && Pin != nullptr); // Ensure we are in the scope of a pin + + return Pin->Connections.empty() == false; +} + +bool ImNodeGraph::IsPinConnected(ImPinPtr pin) +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + // Validate pin scope + ImPinData* Pin = Graph->FindPin(pin); + + if(Pin == nullptr) return false; + + return Pin->Connections.empty() == false; +} + +const ImVector& ImNodeGraph::GetPinConnections() +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + // Validate pin scope + ImPinData* Pin = Graph->CurrentPin; + IM_ASSERT(G.Scope == ImNodeGraphScope_Pin && Pin != nullptr); // Ensure we are in the scope of a pin + + return Pin->Connections; +} + +const ImVector& ImNodeGraph::GetPinConnections(ImPinPtr pin) +{ + static const ImVector Default = ImVector(); + + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + ImPinData* Pin = Graph->FindPin(pin); + if(Pin == nullptr) return Default; + return Pin->Connections; +} + +const ImVector& ImNodeGraph::GetNewConnections() +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + // Validate pin scope + ImPinData* Pin = Graph->CurrentPin; + IM_ASSERT(G.Scope == ImNodeGraphScope_Pin && Pin != nullptr); // Ensure we are in the scope of a pin + + return Pin->NewConnections; +} + +const ImVector& ImNodeGraph::GetErasedConnections() +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + // Validate pin scope + ImPinData* Pin = Graph->CurrentPin; + IM_ASSERT(G.Scope == ImNodeGraphScope_Pin && Pin != nullptr); // Ensure we are in the scope of a pin + + return Pin->ErasedConnections; +} + +int ImNodeGraph::GetUserID(ImPinPtr ptr) +{ + ImNodeData& Node = GImNodeGraph->CurrentGraph->Nodes[ptr.Node]; + return ptr.Direction ? Node.OutputPins[ptr.Pin].UserID : Node.InputPins[ptr.Pin].UserID; +} + +int ImNodeGraph::GetUserID(const char *graph, ImPinPtr ptr) +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = FindGraphByTitle(graph); + + ImNodeData& Node = Graph->Nodes[ptr.Node]; + return ptr.Direction ? Node.OutputPins[ptr.Pin].UserID : Node.InputPins[ptr.Pin].UserID; +} + +ImPinPtr ImNodeGraph::GetPinPtr() +{ + // Validate global state + IM_ASSERT(GImNodeGraph != nullptr); + + // Validate Graph state + ImNodeGraphContext& G = *GImNodeGraph; + ImNodeGraphData* Graph = G.CurrentGraph; + IM_ASSERT(Graph != nullptr); + + // Validate pin scope + return *Graph->CurrentPin; +} diff --git a/imnode_graph.h b/imnode_graph.h new file mode 100755 index 0000000..b83be49 --- /dev/null +++ b/imnode_graph.h @@ -0,0 +1,1500 @@ +// ===================================================================================================================== +// imnode-graph, and open source extension for Dear ImGui that adds functionality for drawing a node graph. +// Copyright (C) 2024 Medusa Slockbower +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ===================================================================================================================== + +#ifndef IMGUI_NODES_H +#define IMGUI_NODES_H + +#include +#include + +// ===================================================================================================================== +// Math +// ===================================================================================================================== + +#include "stdint.h" +#include "math.h" + +template +T ImIsPrime(T x) +{ + if(x <= 1) return false; + if(x == 2 || x == 3) return true; + if(x % 2 == 0 || x % 3 == 0) return false; + + uint64_t limit = static_cast(sqrt(static_cast(x))); + for(T i = 5; i <= limit; i += 6) + { + if(x % i == 0 || x % (i + 2) == 0) return false; + } + + return true; +} + +// ===================================================================================================================== +// Type & Forward Definitions +// ===================================================================================================================== + + +// Data Structures ----------------------------------------------------------------------------------------------------- + +struct ImNodeGraphContext; + +struct ImPinPtr; +struct ImPinConnection; + +template struct ImObjectPool; +template struct ImOptional; + + +// Typedefs ------------------------------------------------------------------------------------------------------------ + +// Graph Types +using ImNodeGraphColor = int; +using ImNodeGraphFlags = int; + +// Pin Types +using ImPinType = int; +using ImPinFlags = int; +using ImPinDirection = bool; + +// Connections +using ImConnectionValidation = bool(*)(ImPinPtr, ImPinPtr); + +// ===================================================================================================================== +// Enums +// ===================================================================================================================== + +enum ImNodeGraphFlags_ +{ + ImNodeGraphFlags_None = 0 +, ImNodeGraphFlags_NoHeader = 1 << 0 +}; + +enum ImNodeGraphColor_ +{ + ImNodeGraphColor_GridBackground +, ImNodeGraphColor_GridPrimaryLines +, ImNodeGraphColor_GridSecondaryLines + +, ImNodeGraphColor_NodeBackground +, ImNodeGraphColor_NodeHoveredBackground +, ImNodeGraphColor_NodeActiveBackground +, ImNodeGraphColor_NodeHeaderColor +, ImNodeGraphColor_NodeHeaderHoveredColor +, ImNodeGraphColor_NodeHeaderActiveColor +, ImNodeGraphColor_NodeOutline +, ImNodeGraphColor_NodeOutlineSelected + +, ImNodeGraphColor_PinBackground + +, ImNodeGraphColor_SelectRegionBackground +, ImNodeGraphColor_SelectRegionOutline + +, ImNodeGraphColor_COUNT +}; + +enum ImPinDirection_ +{ + ImPinDirection_Input = false +, ImPinDirection_Output = true +}; + +enum ImPinFlags_ +{ + ImPinFlags_None = 0 +, ImPinFlags_NoPadding = 1 +}; + +// ===================================================================================================================== +// Data Structures +// ===================================================================================================================== + +/** + * \brief Optional value, similar to std::optional + * \tparam T Value Type + */ +template +struct ImOptional +{ + using value_type = T; + using reference = T&; + using const_reference = const T&; + + T Value; + bool Set; + + ImOptional() : Set(false) { } + ImOptional(const T& value) : Value(value), Set(true) { } + ImOptional(const ImOptional&) = default; + ImOptional(ImOptional&&) = default; + ~ImOptional() = default; + + ImOptional& operator=(const ImOptional& other) = default; + ImOptional& operator=(ImOptional&& other) = default; + + ImOptional& operator=(const T& value) { Value = value; Set = true; return *this; } + ImOptional& operator=(T&& value) { Value = value; Set = true; return *this; } + + bool operator==(const ImOptional &) const = default; + bool operator==(const T& o) const { return Set && Value == o; } + + operator T&() { IM_ASSERT(Set); return Value; } + operator const T&() const { IM_ASSERT(Set); return Value; } + + T* operator->() { IM_ASSERT(Set); return &Value; } + const T* operator->() const { IM_ASSERT(Set); return &Value; } + + bool operator()() const { return Set; } + + void Reset() { Set = false; } +}; + +/** + * \brief Equivalent to std::deque, may be used as a queue, stack, etc. + * \tparam T Value type + */ +template +struct ImDeque +{ + class iterator; + struct Node; + using NodePtr = Node*; + + using value_type = T; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using node_index = int; + + struct Node + { + T Value; + NodePtr Next, Prev; + }; + + NodePtr First, Last; + int Size; + + ImDeque() : First(nullptr), Last(nullptr) { } + ImDeque(const ImDeque&); + ImDeque(ImDeque&&) = default; + ~ImDeque(); + + bool Empty() const { return First == nullptr; } + + void Clear(); + + void PushFront(const T& v); + void PushBack(const T& v); + + void PopFront(); + void PopBack(); + + T& Front() { IM_ASSERT(First); return First->Value; } + const T& Front() const { IM_ASSERT(First); return First->Value; } + + T& Back() { IM_ASSERT(Last); return Last->Value; } + const T& Back() const { IM_ASSERT(Last); return Last->Value; } +}; + + +#ifndef IMSET_MIN_CAPACITY +#define IMSET_MIN_CAPACITY 7 // Should be prime and >= 5 +#endif + +template +size_t ImHash(T v) noexcept; + +template<> +inline size_t ImHash(uint64_t x) noexcept +{ + x ^= x >> 33U; + x *= UINT64_C(0xff51afd7ed558ccd); + x ^= x >> 33U; + x *= UINT64_C(0xc4ceb9fe1a85ec53); + x ^= x >> 33U; + + return x; +} + +template<> inline size_t ImHash(int x) noexcept { return ImHash(static_cast(x)); } +template<> inline size_t ImHash(ImGuiID x) noexcept { return ImHash(static_cast(x)); } + +template<> +inline size_t ImHash(const char* str) noexcept +{ + return ImHash(ImHashStr(str)); +} + +/** + * \brief Set of values, based on Robin Hood Hashing + * \tparam T Value type + */ +template +struct ImSet +{ + struct Node + { + T Value; + bool Set; + int PSL; + + Node() : Value(), Set(false), PSL(0) { } + }; + + class iterator; + + using value_type = T; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator_type = iterator; + + int Size; + int Capacity; + Node* Table; + float LoadFactor; + + ImSet(); + ImSet(const ImSet&); + ImSet(ImSet&&) = default; + ~ImSet(); + + void Clear(); + + void Insert(const T& v); + void Erase(const T& v); + bool Contains(const T& v) { return _Find(v) != -1; } + + int _Find(const T& v); + + bool _CheckLoadFactor(); + void _IncreaseCapacity(); + + int _NextIndex(int i) { return (i + 1) % Capacity; } + int _PrevIndex(int i) { return (i - 1 + Capacity) % Capacity; } + + // These operate on the principle of (6n +/- 1) + int _NextPrime(int x); + int _PrevPrime(int x); + + class iterator + { + public: + iterator(const ImSet* set, int idx); + iterator(const iterator&) = default; + iterator(iterator&&) = default; + ~iterator() = default; + + iterator& operator++(); + iterator operator++(int); + + bool operator==(const iterator& o) const = default; + bool operator!=(const iterator& o) const = default; + + const_reference operator*() const { return set_->Table[idx_].Value; } + const_pointer operator->() const { return &set_->Table[idx_].Value; } + + private: + const ImSet* set_; + int idx_; + }; + + iterator begin() { return iterator(this, 0); } + iterator end() { return iterator(this, Capacity); } + + iterator cbegin() const { return iterator(this, 0); } + iterator cend() const { return iterator(this, Capacity); } +}; + +/** + * \brief Set of values, based on a Red-Black Tree + * \tparam T Value type + */ +template +struct ImOrderedSet +{ + class iterator; + struct Node; + + using NodePtr = Node*; + using NodeColor = bool; + + using value_type = T; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using node_index = int; + using iterator_type = iterator; + + enum NodeColor_ + { + NodeColor_Red = true + , NodeColor_Black = false + }; + + struct Node + { + T Value; + NodeColor Color; + NodePtr Parent, Left, Right; + + Node(const T& v, NodePtr p) : Value(v), Color(NodeColor_Red), Parent(p), Left(nullptr), Right(nullptr) { } + }; + + Node* Root; + int Size; + + ImOrderedSet() : Root(nullptr), Size(0) { } + ImOrderedSet(const ImOrderedSet&) = default; + ImOrderedSet(ImOrderedSet&&) = default; + ~ImOrderedSet(); + + ImOrderedSet& operator=(const ImOrderedSet&) = default; + ImOrderedSet& operator=(ImOrderedSet&& other) = default; + + void Clear(); + void Insert(const T& v); + void Erase(const T& v); + + Node* _Find(const T& v); + + static NodePtr _LeftMost(NodePtr x); + void _RotateLeft(NodePtr x); + void _RotateRight(NodePtr x); + + NodePtr _InsertBST(const T& v); + void _FixInsert(NodePtr x); + + NodePtr _EraseBST(const T& v); + void _FixErase(NodePtr x); + + class iterator + { + public: + iterator(Node* node) : current_(node), visit_queue_() { } + iterator(const iterator&) = default; + iterator(iterator&&) = default; + ~iterator() = default; + + iterator& operator++(); + iterator operator++(int); + + bool operator==(const iterator& o) const { return current_ == o.current_; } + bool operator!=(const iterator& o) const { return current_ != o.current_; } + + const_reference operator*() const { return current_->Value; } + const_pointer operator->() const { return ¤t_->Value; } + + private: + NodePtr current_; + ImDeque visit_queue_; + }; + + iterator begin() { return iterator(_LeftMost(Root)); } + iterator end() { return iterator(nullptr); } +}; + +/** + * \brief Data Structure for holding a pool of objects with generated ids + * \tparam T Value Type + */ +template +struct ImObjectList +{ + class iterator; + + using value_type = T; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator_type = iterator; + + ImVector Data; + ImVector Active; + ImVector Freed; + + ImObjectList() = default; + ImObjectList(const ImObjectList&) = default; + ImObjectList(ImObjectList&&) = default; + ~ImObjectList() = default; + + [[nodiscard]] inline size_t Size() const { return Active.Size; } + + ImGuiID Insert(const T& v); + void Erase(ImGuiID id); + + void Clear() { Data.clear(); Active.clear(); Freed.clear(); } + void Reset() { memset(Active.Data, false, Active.size_in_bytes()); } + void Cleanup(); + + T& operator[](ImGuiID id) { IM_ASSERT(Active[id]); return Data[id]; } + const T& operator[](ImGuiID id) const { IM_ASSERT(Active[id]); return Data[id]; } + bool operator()(ImGuiID id) const { return Active[id]; } + + class iterator + { + public: + iterator(ImObjectList& pool, int idx); + + iterator& operator++(); + iterator operator++(int); + + bool operator==(const iterator&) const = default; + bool operator!=(const iterator&) const = default; + + reference operator*() const { return (*pool_)[idx_]; } + pointer operator->() const { return &(*pool_)[idx_]; } + + private: + ImObjectList* pool_; + int idx_; + }; + + iterator begin() { return iterator(*this, 0); } + iterator end() { return iterator(*this, Active.size()); } + + class const_iterator + { + public: + const_iterator(const ImObjectList& pool, int idx); + + const_iterator& operator++(); + const_iterator operator++(int); + + bool operator==(const const_iterator&) const = default; + bool operator!=(const const_iterator&) const = default; + + const_reference operator*() const { return (*pool_)[idx_]; } + const_pointer operator->() const { return &(*pool_)[idx_]; } + + private: + const ImObjectList* pool_; + int idx_; + }; + + const_iterator begin() const { return const_iterator(*this, 0); } + const_iterator end() const { return const_iterator(*this, Active.size()); } +}; + +/** + * \brief Data Structure for holding a pool of objects with user provided ids + * \tparam T Value Type + */ +template +struct ImObjectPool +{ + class iterator; + class reverse_iterator; + + static constexpr int nullidx = -1; + using value_type = T; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator_type = iterator; + + ImVector Data; + ImVector Active; + ImVector Freed; + ImVector Order; + ImVector IdxToID; + ImGuiStorage IDToIdx; + + ImObjectPool() = default; + ImObjectPool(const ImObjectPool&) = default; + ImObjectPool(ImObjectPool&&) = default; + ~ImObjectPool() = default; + + [[nodiscard]] inline size_t Size() const { return Order.Size; } + + void Clear() { Data.clear(); Active.clear(); Freed.clear(); Order.clear(); IDToIdx.Clear(); IdxToID.clear(); } + void Reset() { memset(Active.Data, false, Active.size_in_bytes()); } + int Cleanup(); + void PushToTop(ImGuiID id); + + ImObjectPool& operator=(const ImObjectPool&) = default; + + T& operator[](ImGuiID id); + const T& operator[](ImGuiID id) const { int idx = IDToIdx.GetInt(id, nullidx); IM_ASSERT(idx != nullidx && Active[idx]); return Data[idx]; }; + bool operator()(ImGuiID id) const { int idx = IDToIdx.GetInt(id, nullidx); return idx != nullidx && Active[idx]; } + + T& operator[](int idx) { IM_ASSERT(idx >= 0 && idx < Data.Size); return Data[Order[idx]]; } + const T& operator[](int idx) const { IM_ASSERT(idx >= 0 && idx < Data.Size); return Data[Order[idx]]; } + bool operator()(int idx) const { IM_ASSERT(idx >= 0 && idx < Data.Size); return Active[Order[idx]]; } + + class iterator + { + public: + iterator(ImObjectPool& pool, int idx); + + iterator& operator++(); + iterator operator++(int); + + bool operator==(const iterator&) const = default; + bool operator!=(const iterator&) const = default; + + reference operator*() const { return (*pool_)[idx_]; } + pointer operator->() const { return &(*pool_)[idx_]; } + + private: + ImObjectPool* pool_; + int idx_; + }; + + iterator begin() { return iterator(*this, 0); } + iterator end() { return iterator(*this, Order.size()); } + + class reverse_iterator + { + public: + reverse_iterator(ImObjectPool& pool, int idx); + + reverse_iterator& operator++(); + reverse_iterator operator++(int); + + bool operator==(const reverse_iterator&) const = default; + bool operator!=(const reverse_iterator&) const = default; + + reference operator*() const { return (*pool_)[idx_ - 1]; } + pointer operator->() const { return &(*pool_)[idx_ - 1]; } + + private: + ImObjectPool* pool_; + int idx_; + }; + + reverse_iterator rbegin() { return reverse_iterator(*this, Order.size()); } + reverse_iterator rend() { return reverse_iterator(*this, 0); } + + +private: + int _GetNextIndex(ImGuiID id); + void _PushBack(ImGuiID id); +}; + +struct ImGraphCamera +{ + ImVec2 Position; + float Scale; + + ImGraphCamera(); +}; + +struct ImNodeGraphStyle +{ + float GridPrimaryStep; + float GridPrimaryThickness; + float GridSecondaryThickness; + + float NodeRounding; + float NodePadding; + float NodeOutlineThickness; + float NodeOutlineSelectedThickness; + + float SelectRegionRounding; + float SelectRegionOutlineThickness; + + float ItemSpacing; + float PinRadius; + float PinOutlineThickness; + + float ConnectionThickness; + + ImColor Colors[ImNodeGraphColor_COUNT]; + const ImColor* PinColors; + + ImNodeGraphStyle(); + ImNodeGraphStyle(const ImNodeGraphStyle&) = default; + + ImU32 GetColorU32(ImNodeGraphColor idx) const { return Colors[idx]; } + ImVec4 GetColorVec4(ImNodeGraphColor idx) const { return Colors[idx]; } +}; + +struct ImNodeGraphSettings +{ + float ZoomRate; + float ZoomSmoothing; + ImVec2 ZoomBounds; + + ImNodeGraphSettings(); +}; + +struct ImPinPtr +{ + ImGuiID Node; + ImGuiID Pin; + ImPinDirection Direction; + + bool operator==(const ImPinPtr&) const = default; +}; + +struct ImUserPinPtr +{ + int Node; + int Pin; + ImPinDirection Direction; + + bool operator==(const ImUserPinPtr&) const = default; + + ImUserPinPtr() : Node(0), Pin(0), Direction(ImPinDirection_Input) { } + ImUserPinPtr(int n, int p, ImPinDirection d) : Node(n), Pin(p), Direction(d) { } +}; + +struct ImPinConnection +{ + ImPinPtr A, B; +}; + +// ===================================================================================================================== +// Functionality +// ===================================================================================================================== + +namespace ImNodeGraph +{ +// Context ------------------------------------------------------------------------------------------------------------- + + /** + * \brief Setup the ImNodeGraph Context, must be called after ImGui::CreateContext() + */ + ImNodeGraphContext* CreateContext(); + + /** + * \brief Cleanup the ImNodeGraph Context, must be called before ImGui::DestroyContext() + */ + void DestroyContext(ImNodeGraphContext* ctx = NULL); + + /** + * \brief Getter for the current context + * \return Pointer to the current context + */ + ImNodeGraphContext* GetCurrentContext(); + void SetCurrentContext(ImNodeGraphContext* ctx); + + void AddFont(const char* path, float size = 0, const ImWchar* glyph_ranges = nullptr); + + +// Graph --------------------------------------------------------------------------------------------------------------- + + /** + * \brief Push a new graph to a window + * \param title Title for the graph + * \param size_arg Size of the graph, + */ + void BeginGraph(const char* title, const ImVec2& size_arg = { 0, 0 }); + void EndGraph(); + + /** + * \brief Begin an operation on the graph after it has already been drawn + * \param title Title of the graph to operate on + */ + bool BeginGraphPostOp(const char *title); + void EndGraphPostOp(); + + /** + * \brief Set a validation function to check the validity of connections + * \param validation + */ + void SetGraphValidation(ImConnectionValidation validation); + + /** + * \brief Helper to get the scale of the camera for the current graph + * \return + */ + float GetCameraScale(); + + // Functions to transform locations + ImVec2 GridToWindow(const ImVec2& pos); + ImVec2 WindowToScreen(const ImVec2& pos); + ImVec2 GridToScreen(const ImVec2& pos); + + ImVec2 ScreenToGrid(const ImVec2& pos); + ImVec2 ScreenToWindow(const ImVec2& pos); + ImVec2 WindowToGrid(const ImVec2& pos); + + ImVec2 SnapToGrid(const ImVec2& pos); + + // Camera correct ImGui::PushItemWidth() + void PushItemWidth(float width); + + const ImObjectList& GetConnections(); + + void ResetGraph(); + void ResetGraph(const char* graph); + + +// Nodes --------------------------------------------------------------------------------------------------------------- + + /** + * \brief Push a new node to add widgets to in a window + * \param id Id of the node + * \return False if the node is collapsed + */ + void BeginNode(int id, ImVec2& pos); + void EndNode(); + + /** + * \brief Write a header for the node + * \param title Title of the header + * \param color Base color + * \param hovered Color for when hovered by mouse + * \param active Color for when clicked on by mouse + */ + void BeginNodeHeader(const char* title, ImColor color, ImColor hovered, ImColor active); + void BeginNodeHeader(int id, ImColor color, ImColor hovered, ImColor active); + void EndNodeHeader(); + + /** + * \brief Get a set of the currently selected nodes + * \return + */ + ImSet* GetSelected(); + ImSet* GetSelected(const char* title); + + /** + * \brief Get the user id for a given node id + * \param id ImNodeGraph id for the node + * \return User provided id for the node + */ + int GetUserID(ImGuiID id); + int GetUserID(const char* graph, ImGuiID id); + + +// Pins ---------------------------------------------------------------------------------------------------------------- + + /** + * \brief Set the colors attributed to each pin type + * \param colors Array of color values + */ + void SetPinColors(const ImColor* colors); + + /** + * \brief Add a pin to the node + * \param id Id of the pin + * \param type Type of the pin + * \param direction Direction of the pin + */ + bool BeginPin(int id, ImPinType type, ImPinDirection direction, ImPinFlags flags = 0); + void EndPin(); + + // Functions to check connection status + bool IsPinConnected(); + bool IsPinConnected(ImPinPtr pin); + const ImVector& GetPinConnections(); + const ImVector& GetPinConnections(ImPinPtr pin); + const ImVector& GetNewConnections(); + const ImVector& GetErasedConnections(); + + // Get qualified pointer to the currently selected pin + ImPinPtr GetPinPtr(); + + /** + * \brief Get the user id for a given pin id + * \param ptr ImNodeGraph id for the pin + * \return User provided id for the pin + */ + int GetUserID(ImPinPtr ptr); + int GetUserID(const char* graph, ImPinPtr ptr); + + +// Connections --------------------------------------------------------------------------------------------------------- + + // Create and break connections between pins + bool MakeConnection(const ImPinPtr& a, const ImPinPtr& b); + bool MakeConnection(const ImUserPinPtr& a, const ImUserPinPtr& b); + + void BreakConnection(ImGuiID connection); + void BreakConnections(const ImPinPtr& pin); + bool Exists(ImPinConnection connection); + + // Get the pins associated with the provided connection id + ImPinConnection GetConnection(ImGuiID connection); +} + +// ===================================================================================================================== +// Template Implementations +// ===================================================================================================================== + + +// ImDeque ------------------------------------------------------------------------------------------------------------- + +template +ImDeque::~ImDeque() +{ + Clear(); +} + +template +void ImDeque::Clear() +{ + while(First) + { + NodePtr x = First; + First = First->Next; + delete x; + } + + First = Last = nullptr; + Size = 0; +} + +template +void ImDeque::PushFront(const T &v) +{ + First = new Node{ v, First, nullptr }; + if(Last == nullptr) Last = First; + ++Size; +} + +template +void ImDeque::PushBack(const T &v) +{ + Last = new Node{ v, nullptr, Last }; + if(First == nullptr) First = Last; + ++Size; +} + +template +void ImDeque::PopFront() +{ + Node* erase = First; + First = First->Next; + if(First) First->Prev = nullptr; + else Last = nullptr; + delete erase; + --Size; +} + +template +void ImDeque::PopBack() +{ + Node* erase = Last; + Last = Last->Prev; + if(Last) Last->Prev = nullptr; + else First = nullptr; + delete erase; + --Size; +} + + +// ImSet -------------------------------------------------------------------------------------------------------- + +template +ImSet::ImSet() + : Table(nullptr) + , Size(0) + , Capacity(0) + , LoadFactor(0.8f) +{ + +} + +template +ImSet::ImSet(const ImSet& o) + : Table(nullptr) + , Size(o.Size) + , Capacity(o.Capacity) + , LoadFactor(o.LoadFactor) +{ + Table = (Node*)IM_ALLOC(Capacity * sizeof(Node)); + memcpy(Table, o.Table, Capacity * sizeof(Node)); +} + +template +ImSet::~ImSet() +{ + Clear(); +} + +template +void ImSet::Clear() +{ + if(Table) IM_FREE(Table); + Table = nullptr; + Size = Capacity = 0; +} + +template +void ImSet::Insert(const T &v) +{ + if(_CheckLoadFactor()) _IncreaseCapacity(); + + int idx = ImHash(v) % Capacity; + int PSL = 0; + T Value = v; + + while(Table[idx].Set) + { + Node& node = Table[idx]; + if(Table[idx].Value == v) return; + if(PSL > node.PSL) // Higher PSL, Swap + { + ImSwap(PSL, node.PSL); + ImSwap(Value, node.Value); + } + idx = _NextIndex(idx); + ++PSL; + } + + Table[idx].Value = Value; + Table[idx].Set = true; + Table[idx].PSL = PSL; + ++Size; +} + +template +void ImSet::Erase(const T &v) +{ + int idx = _Find(v); + if(idx == -1) return; + + Table[idx].Set = false; + --Size; + + int prev = idx; idx = _NextIndex(idx); + while(Table[idx].Set && Table[idx].PSL > 0) + { + Node &a = Table[prev], &b = Table[idx]; + ImSwap(a, b); + --a.PSL; prev = idx; idx = _NextIndex(idx); + } +} + +template +int ImSet::_Find(const T &v) +{ + // Can be improved, not necessary for the needs of this library + if(Capacity == 0) return -1; + + int idx = ImHash(v) % Capacity; + int PSL = 0; + + while(Table[idx].Set) + { + Node& node = Table[idx]; + + if(node.PSL > PSL) return -1; + if(node.Value == v) return idx; + + idx = _NextIndex(idx); ++PSL; + } + + return -1; +} + +template +bool ImSet::_CheckLoadFactor() +{ + if(Capacity == 0) return true; + float load = Size / static_cast(Capacity); + return load >= LoadFactor; +} + +template +void ImSet::_IncreaseCapacity() +{ + Node* old = Table; + int old_capacity = Capacity; + + Capacity = _NextPrime(Capacity); + + Table = static_cast(IM_ALLOC(Capacity * sizeof(Node))); + memset(Table, 0, Capacity * sizeof(Node)); + Size = 0; + + for(int i = 0; i < old_capacity; ++i) + { + if(old[i].Set) Insert(old[i].Value); + } + + IM_FREE(old); +} + +template +int ImSet::_NextPrime(int x) +{ + int n = (x + 1) / 6; + n *= 2; + + while(true) + { + x = (n * 6) - 1; + if(!ImIsPrime(x)) x = (n * 6) + 1; + if(!ImIsPrime(x)) { ++n; continue; } + return x < IMSET_MIN_CAPACITY ? IMSET_MIN_CAPACITY : x; + } +} + +template +int ImSet::_PrevPrime(int x) +{ + int n = (x + 1) / 6; + n /= 2; + + while(true) + { + x = (n * 6) - 1; + if(!ImIsPrime(x)) x = (n * 6) + 1; + if(!ImIsPrime(x)) { --n; continue; } + return x < IMSET_MIN_CAPACITY ? IMSET_MIN_CAPACITY : x; + } +} + +template +ImSet::iterator::iterator(const ImSet* set, int idx) + : set_(set), idx_(idx) +{ + while(idx_ < set_->Capacity && set_->Table[idx_].Set == false) + { + ++idx_; + } +} + +template +typename ImSet::iterator & ImSet::iterator::operator++() +{ + ++idx_; + + while(idx_ < set_->Capacity && set_->Table[idx_].Set == false) + { + ++idx_; + } + + return *this; +} + +template +typename ImSet::iterator ImSet::iterator::operator++(int) +{ + iterator ret = *this; + ++idx_; + + while(idx_ < set_->Capacity && set_->Table[idx_].Set == false) + { + ++idx_; + } + + return ret; +} + + +// ImOrderedSet -------------------------------------------------------------------------------------------------------- + +template +ImOrderedSet::~ImOrderedSet() +{ + Clear(); +} + +template +void ImOrderedSet::Clear() +{ + ImDeque queue; + queue.PushBack(Root); + + while(queue.Empty() == false) + { + NodePtr x = queue.Front(); queue.PopFront(); + if(x->Left) queue.PushBack(x->Left); + if(x->Right) queue.PushBack(x->Right); + delete x; + } + + Root = nullptr; +} + +template +void ImOrderedSet::Insert(const T& value) +{ + NodePtr node = _InsertBST(value); + if(node) _FixInsert(node); +} + +template +void ImOrderedSet::Erase(const T &v) +{ +} + +template +typename ImOrderedSet::Node* ImOrderedSet::_Find(const T &v) +{ + NodePtr x = Root; + + while(x) + { + if(v < x->Value) x = x->Left; + else if(x->Value < v) x = x->Right; + else return x; + } + + return x; +} + +template +ImOrderedSet::NodePtr ImOrderedSet::_LeftMost(NodePtr x) +{ + if(x == nullptr) return nullptr; + while(x->Left) x = x->Left; + return x; +} + +template +void ImOrderedSet::_RotateLeft(NodePtr x) +{ + NodePtr y = x->Right; + x->Right = y->Left; + + if(y->Left) y->Left->Parent = x; + + y->Parent = x->Parent; + + if(x->Parent == nullptr) Root = y; + else if(x == x->Parent->Left) x->Parent->Left = y; + else x->Parent->Right = y; + + y->Left = x; + x->Parent = y; +} + +template +void ImOrderedSet::_RotateRight(NodePtr x) +{ + NodePtr y = x->Left; + x->Left = y->Right; + + if(y->Right) y->Right->Parent = x; + + y->Parent = x->Parent; + + if(x->Parent == nullptr) Root = y; + else if(x == x->Parent->Right) x->Parent->Right = y; + else x->Parent->Left = y; + + y->Right = x; + x->Parent = y; +} + +template +typename ImOrderedSet::NodePtr ImOrderedSet::_InsertBST(const T &v) +{ + NodePtr *x = &Root; + NodePtr p = nullptr; + + while(*x) + { + NodePtr n = *x; + + if(v < n->Value) x = &n->Left; + else if(n->Value < v) x = &n->Right; + else return nullptr; + + p = n; + } + + return *x = new Node(v, p); +} + +template +void ImOrderedSet::_FixInsert(NodePtr x) +{ + while (x != Root && x->Parent->Color == NodeColor_Red) + { + NodePtr p = x->Parent; + NodePtr g = p->Parent; + bool s = p == g->Left; + NodePtr u = s ? g->Right : g->Left; + + if (u->Color == NodeColor_Red) + { + p->Color = NodeColor_Black; + u->Color = NodeColor_Black; + g->Color = NodeColor_Red; + x = g; + } + else if(s) // Best for readability and reducing the number of branches + { + if (x == p->Right) + { + x = x->Parent; + _RotateLeft(x); + } + x->Parent->Color = NodeColor_Black; + x->Parent->Parent->Color = NodeColor_Red; + _RotateRight(x->Parent->Parent); + } + else + { + if (x == p->Left) + { + x = x->Parent; + _RotateRight(x); + } + x->Parent->Color = NodeColor_Black; + x->Parent->Parent->Color = NodeColor_Red; + _RotateLeft(x->Parent->Parent); + } + } + Root->Color = NodeColor_Black; +} + +template +typename ImOrderedSet::NodePtr ImOrderedSet::_EraseBST(const T &v) +{ + NodePtr x = _Find(v); +} + +template +typename ImOrderedSet::iterator & ImOrderedSet::iterator::operator++() +{ + NodePtr x = current_; + NodePtr p = x->Parent; + + if(p && x == p->Left) visit_queue_.PushBack(p); + if(x->Right) visit_queue_.PushBack(_LeftMost(x->Right)); + + if(visit_queue_.Empty()) current_ = nullptr; + else { current_ = visit_queue_.Back(); visit_queue_.PopBack(); } + + return *this; +} + +template +typename ImOrderedSet::iterator ImOrderedSet::iterator::operator++(int) +{ + iterator ret = *this; + NodePtr x = current_; + NodePtr p = x->Parent; + + if(p && x == p->Left) visit_queue_.PushBack(p); + if(x->Right) visit_queue_.PushBack(_LeftMost(x->Right)); + + if(visit_queue_.Empty()) current_ = nullptr; + else { current_ = visit_queue_.Back(); visit_queue_.PopBack(); } + + return ret; +} + + +// ImObjectList -------------------------------------------------------------------------------------------------------- + +template +ImGuiID ImObjectList::Insert(const T &v) +{ + if(Freed.empty()) + { + Data.push_back(v); Active.push_back(true); + return Data.Size - 1; + } + + ImGuiID id = Freed.back(); Freed.pop_back(); + Data[id] = v; Active[id] = true; + return id; +} + +template +void ImObjectList::Erase(ImGuiID id) +{ + Active[id] = false; Freed.push_back(id); + Data[id] = T(); +} + +template +void ImObjectList::Cleanup() +{ + Freed.Size = 0; + for(int i = 0; i < Active.Size; ++i) + { + if(Active[i]) continue; + Freed.push_back(i); + } +} + +template +ImObjectList::iterator::iterator(ImObjectList &pool, int idx) + : pool_(&pool) + , idx_(idx) +{ + while(idx_ < pool_->Size() && !(*pool_)(idx_)) ++idx_; +} + +template +typename ImObjectList::iterator & ImObjectList::iterator::operator++() +{ + ++idx_; + while(idx_ < pool_->Size() && !(*pool_)(idx_)) ++idx_; + return *this; +} + +template +typename ImObjectList::iterator ImObjectList::iterator::operator++(int) +{ + const_iterator retval = *this; + ++idx_; + while(idx_ < pool_->Size() && !(*pool_)(idx_)) ++idx_; + return retval; +} + +template +ImObjectList::const_iterator::const_iterator(const ImObjectList &pool, int idx) + : pool_(&pool) + , idx_(idx) +{ + while(idx_ < pool_->Size() && !(*pool_)(idx_)) ++idx_; +} + +template +typename ImObjectList::const_iterator & ImObjectList::const_iterator::operator++() +{ + ++idx_; + while(idx_ < pool_->Size() && !(*pool_)(idx_)) ++idx_; + return *this; +} + +template +typename ImObjectList::const_iterator ImObjectList::const_iterator::operator++(int) +{ + const_iterator retval = *this; + ++idx_; + while(idx_ < pool_->Size() && !(*pool_)(idx_)) ++idx_; + return retval; +} + + +// ImObjectPool -------------------------------------------------------------------------------------------------------- + +template +int ImObjectPool::Cleanup() +{ + int cnt = Freed.Size; + Freed.Size = 0; + for(int i = 0; i < Active.Size; ++i) + { + if(Active[i]) continue; + + Freed.push_back(i); + Order.find_erase(i); + IDToIdx.SetInt(IdxToID[i], nullidx); + IdxToID[i] = 0; + } + return Freed.Size - cnt; +} + +template +T& ImObjectPool::operator[](ImGuiID id) +{ + int idx = IDToIdx.GetInt(id, nullidx); // Get the mapped index + if(idx == nullidx) + { + idx = _GetNextIndex(id); // If it is unassigned, get the next available index + Order.push_back(idx); + } + + Active[idx] = true; + return Data[idx]; +} + +template +void ImObjectPool::PushToTop(ImGuiID id) // Should always be O(n) +{ + int idx = IDToIdx.GetInt(id, nullidx); + if(idx == nullidx) return; + + for(int i = Order.find_index(idx); i < Order.size() - 1; ++i) + { + ImSwap(Order[i], Order[i + 1]); + } +} + +template +int ImObjectPool::_GetNextIndex(ImGuiID id) +{ + int idx = Data.Size; // Default to size of data array + if(!Freed.empty()) // If there are freed indices, pop one + { + idx = Freed.back(); Freed.pop_back(); + Data[idx] = T(); Active[idx] = true; IdxToID[idx] = id; // Reset index values + } + else _PushBack(id); // Otherwise, push back new index + IDToIdx.SetInt(id, idx); + return idx; +} + +template +void ImObjectPool::_PushBack(ImGuiID id) +{ + Data.push_back(T()); + Active.push_back(true); + IdxToID.push_back(id); +} + +template +ImObjectPool::iterator::iterator(ImObjectPool &pool, int idx) + : pool_(&pool) + , idx_(idx) +{ + while(idx_ < pool_->Size() && !(*pool_)(idx_)) ++idx_; +} + +template +typename ImObjectPool::iterator & ImObjectPool::iterator::operator++() +{ + ++idx_; + while(idx_ < pool_->Size() && !(*pool_)(idx_)) ++idx_; + return *this; +} + +template +typename ImObjectPool::iterator ImObjectPool::iterator::operator++(int) +{ + iterator retval = *this; + ++idx_; + while(idx_ < pool_->Size() && !(*pool_)(idx_)) ++idx_; + return retval; +} + +template +ImObjectPool::reverse_iterator::reverse_iterator(ImObjectPool &pool, int idx) + : pool_(&pool) + , idx_(idx) +{ + while(idx_ > 0 && !(*pool_)(idx_ - 1)) --idx_; +} + +template +typename ImObjectPool::reverse_iterator & ImObjectPool::reverse_iterator::operator++() +{ + --idx_; + while(idx_ > 0 && !(*pool_)(idx_ - 1)) --idx_; + return *this; +} + +template +typename ImObjectPool::reverse_iterator ImObjectPool::reverse_iterator::operator++(int) +{ + iterator retval = *this; + --idx_; + while(idx_ > 0 && !(*pool_)(idx_ - 1)) --idx_; + return retval; +} + +#endif //IMGUI_NODES_H diff --git a/imnode_graph.natvis b/imnode_graph.natvis new file mode 100755 index 0000000..b1e8fe5 --- /dev/null +++ b/imnode_graph.natvis @@ -0,0 +1,35 @@ + + + + + + + + {...} + + + Capacity + Table + + + + + \ No newline at end of file diff --git a/imnode_graph_internal.h b/imnode_graph_internal.h new file mode 100755 index 0000000..ee3227a --- /dev/null +++ b/imnode_graph_internal.h @@ -0,0 +1,277 @@ +// ===================================================================================================================== +// imnode-graph, and open source extension for Dear ImGui that adds functionality for drawing a node graph. +// Copyright (C) 2024 Medusa Slockbower +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ===================================================================================================================== + +#ifndef IMGUI_NODES_INTERNAL_H +#define IMGUI_NODES_INTERNAL_H + +#include "imnode_graph.h" + +// ===================================================================================================================== +// Type & Forward Definitions +// ===================================================================================================================== + +// Typedefs +using ImNodeGraphScope = int; + +// Data Structures +struct ImNodeGraphContext; +struct ImNodeGraphData; +struct ImNodeData; +struct ImNodeHeaderData; +struct ImPinData; +struct ImRect; + +// ===================================================================================================================== +// Enums +// ===================================================================================================================== + +enum ImNodeGraphScope_ +{ + ImNodeGraphScope_None = 0 +, ImNodeGraphScope_Graph +, ImNodeGraphScope_Node +, ImNodeGraphScope_NodeHeader +, ImNodeGraphScope_Pin +}; + + +// ===================================================================================================================== +// Math +// ===================================================================================================================== + +bool ImAABB(const ImRect& a, const ImRect& b); + +// ===================================================================================================================== +// Data Structures +// ===================================================================================================================== + +/** + * \brief Context for node graph system + */ +struct ImNodeGraphContext +{ + bool Initialized; + ImVector Fonts; + ImNodeGraphScope Scope; + + ImVector Graphs; + ImGuiStorage GraphsById; + ImNodeGraphData* CurrentGraph; + + ImNodeGraphContext(); +}; + +/** + * \brief Data structure for a graph instance + */ +struct ImNodeGraphData +{ + // Context & Style Vars + ImNodeGraphContext* Ctx; + ImNodeGraphFlags Flags; + char* Name; + ImGuiID ID; + ImNodeGraphStyle Style; + ImNodeGraphSettings Settings; + ImVec2 ScreenPos, ScreenSize; + + // Camera Vars + ImGraphCamera Camera; + float TargetZoom; + bool IsPanning; + + // Input Vars + ImOptional SelectRegionStart; + ImSet SelectRegion; + ImSet Selected; + ImVec2 DragOffset; + bool Dragging, LockSelectRegion; + + // Node & Pin Vars + ImObjectPool Nodes; + ImOptional HoveredNode, FocusedNode; + ImOptional HoveredPin, FocusedPin; + ImNodeData* CurrentNode; + ImPinData* CurrentPin; + int SubmitCount; + + // Connections + ImOptional NewConnection; // For dragging pins + ImObjectList Connections; + ImConnectionValidation Validation; + + ImNodeGraphData(ImNodeGraphContext* ctx, const char* name); + + void Reset(); + + ImPinData* FindPin(ImPinPtr pin); + ImNodeData* FindNode(int id); + ImPinData* FindPin(ImUserPinPtr pin); + + [[nodiscard]] ImVec2 GetCenter() const { return ScreenPos + ScreenSize * 0.5; } + + ImRect GetSelection(); + + void UpdateSelection(ImGuiID node, bool allow_clear = false, bool removal = false); + + operator ImGuiID() const { return ID; } +}; + +struct ImNodeHeaderData +{ + ImNodeData* Node; + ImColor Color; + ImRect ScreenBounds; + + ImNodeHeaderData() : Node(nullptr), Color(0, 0, 0, 0), ScreenBounds(0, 0, 0, 0) { } + ImNodeHeaderData(ImNodeData* node, ImColor color, ImRect screen_bounds) : Node(node), Color(color), ScreenBounds(screen_bounds) { } + ImNodeHeaderData(const ImNodeHeaderData&) = default; + + ImNodeHeaderData& operator=(const ImNodeHeaderData&) = default; +}; + +/** + * \brief Data structure for nodes + */ +struct ImNodeData +{ + ImNodeGraphData* Graph; + ImGuiID ID; + int UserID; + ImVec2 Root; + ImRect ScreenBounds; + int BgChannelIndex, FgChannelIndex; + bool Hovered, Active; + ImVec2 DragOffset; + ImGuiID PrevActiveItem, ActiveItem; + + ImOptional Header; + ImOptional PinOffset; + ImObjectPool InputPins; + ImObjectPool OutputPins; + + ImNodeData(); + ImNodeData(const ImNodeData&); + ~ImNodeData() = default; + + ImNodeData& operator=(const ImNodeData&); + + operator ImGuiID() const { return ID; } +}; + +/** + * \brief Data structure for pins + */ +struct ImPinData +{ + // Pin Info + ImGuiID Node; + ImGuiID ID; + int UserID; + ImPinType Type; + ImPinDirection Direction; + ImPinFlags Flags; + ImVec2 Pos, Center; + ImRect ScreenBounds; + ImVector Connections; + ImVector NewConnections, ErasedConnections; + bool BNewConnections, BErasedConnections; + + // Input + bool Hovered; + + ImPinData(); + ImPinData(const ImPinData&) = default; + ~ImPinData() = default; + + ImPinData& operator=(const ImPinData&) = default; + + operator ImPinPtr() const { return { Node, ID, Direction }; } +}; + +// ===================================================================================================================== +// Functionality +// ===================================================================================================================== + +// Extensions +namespace ImGui +{ + bool IsAnyModKeyDown(); +} + +namespace ImNodeGraph +{ +// Context ------------------------------------------------------------------------------------------------------------- + + void Initialize(); + void Shutdown(); + void LoadFonts(); + void LoadDefaultFont(); + +// Graph --------------------------------------------------------------------------------------------------------------- + + ImNodeGraphData* FindGraphByID(ImGuiID id); + ImNodeGraphData* FindGraphByTitle(const char* title); + ImNodeGraphData* CreateNewGraph(const char* title); + + void DrawGrid(const ImRect& grid_bounds); + void GraphBehaviour(const ImRect& grid_bounds); + void DrawGraph(ImNodeGraphData* graph); + void DrawSelection(ImNodeGraphData* graph); + + int PushChannels(int count); + void SetChannel(ImGuiID id); + void SwapChannel(ImDrawChannel& a, ImDrawChannel& b); + void SortChannels(); + + bool CheckConnectionValidity(ImGuiID id, ImPinConnection& connection); + void CleanupConnection(ImGuiID id, ImPinConnection& connection); + +// Nodes --------------------------------------------------------------------------------------------------------------- + + void DrawNode(ImNodeData* node); + bool NodeBehaviour(ImNodeData* node); + +// Pins ---------------------------------------------------------------------------------------------------------------- + + void PinHead(ImGuiID id, ImPinData* pin); + void DummyPinHead(ImPinData* pin); + +// Connections --------------------------------------------------------------------------------------------------------- + + void BeginConnection(const ImPinPtr &pin); + + void DrawConnection(const ImVec2& out, const ImVec4& out_col, const ImVec2& in, const ImVec4& in_col); + void DrawConnection(const ImPinData* pin, const ImVec2 &point); + void DrawConnection(const ImPinData* a, const ImPinData* b); + + ImVec2 PinConnectionAnchor(const ImPinData* a); + + void AddPolylineMultiColored(ImDrawList& draw_list, const ImVec2 *points, int num_points, + const ImVec4& c1, const ImVec4& c2, ImDrawFlags flags, float thickness); + + void AddBezierCubicMultiColored(ImDrawList& draw_list, const ImVec2& p1, const ImVec2& p2, + const ImVec2& p3, const ImVec2& p4, const ImVec4 &c1, const ImVec4 &c2, float thickness, + int num_segments = 0); + + inline void PathStrokeMultiColored(ImDrawList& draw_list, const ImVec4& c1, const ImVec4& c2, + ImDrawFlags flags = 0, float thickness = 1.0f) + { AddPolylineMultiColored(draw_list, draw_list._Path.Data, draw_list._Path.Size, c1, c2, flags, thickness); draw_list._Path.Size = 0; } +} + +#endif //IMGUI_NODES_INTERNAL_H diff --git a/imnode_graph_math.h b/imnode_graph_math.h new file mode 100755 index 0000000..f932bd9 --- /dev/null +++ b/imnode_graph_math.h @@ -0,0 +1,26 @@ +// ===================================================================================================================== +// imnode-graph, and open source extension for Dear ImGui that adds functionality for drawing a node graph. +// Copyright (C) 2024 Medusa Slockbower +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ===================================================================================================================== + +#ifndef IMNODE_GRAPH_MATH_H +#define IMNODE_GRAPH_MATH_H + +#include "imgui-docking/imgui_internal.h" + +template static inline T ImMod(const T& a, const T& b) { return a - b * ImFloor(a / b); } + +#endif //IMNODE_GRAPH_MATH_H